算法 - 排序 - 堆排序

1. 簡介

堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。

堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:
1.堆中某個節點的值總是不大於或不小於其父節點的值;
2.堆總是一棵完全二叉樹。

常見的堆有二叉堆、斐波那契堆等。

堆的定義:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關係時,稱之爲堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)

2. 算法演示

排序動畫過程解釋

  1. 首先,將所有的數字存儲在堆中

  2. 按大頂堆構建堆,其中大頂堆的一個特性是數據將被從大到小取出,將取出的數字按照相反的順序進行排列,數字就完成了排序

  3. 在這裏數字 5 先入堆

  4. 數字 2 入堆

  5. 數字 7 入堆, 7 此時是最後一個節點,與最後一個非葉子節點(也就是數字 5 )進行比較,由於 7 大於 5 ,所以 7 和 5 交互

  6. 按照上述的操作將所有數字入堆,然後從左到右,從上到下進行調整,構造出大頂堆

  7. 入堆完成之後,將堆頂元素取出,將末尾元素置於堆頂,重新調整結構,使其滿足堆定義

  8. 堆頂元素數字 7 取出,末尾元素數字 4 置於堆頂,爲了維護好大頂堆的定義,最後一個非葉子節點數字 5 與 4 比較,而後交換兩個數字的位置

  9. 反覆執行調整+交換步驟,直到整個序列有序

3. 代碼


/**
 * Created by lizq on 2020/3/3.
 */
public class HeapSort {

    public static void main(String[] args) {
        long[] arrs = RandomArr.createLongArr(20, 0, 200);
        long[] arrs2 = Arrays.copyOf(arrs, arrs.length);
        Arrays.stream(arrs).forEach(l -> {
            System.out.print(l + " ");
        });
        System.out.println();
        sort(arrs);

        Arrays.stream(arrs).forEach(l -> {
            System.out.print(l + " ");
        });
        System.out.println();
        Arrays.stream(arrs2).sorted().forEach(l -> {
            System.out.print(l + " ");
        });
    }

    public static void sort(long[] arrs) {
        buildHeap(arrs);
        int len = arrs.length;
        for (int i = len - 1; i > 0; i--) {
            swap(arrs, 0, i);
            len--;
            heapify(arrs, 0, len);
        }
    }


    public static void buildHeap(long[] arrs) {
        // 找到最後一個沒有子樹的節點
        for (int i = (int) Math.floor(arrs.length / 2); i >= 0; i--) {
            heapify(arrs, i, arrs.length);
        }
    }


    public static void heapify(long[] arrs, int i, int len) {

        int l = getLeft(i);
        int r = getRight(i);
        long tmp;
        int largest = i;

        // 如果左子樹大於根節點,則把左子樹設置最大值
        if (l < len && arrs[l] > arrs[largest]) {
            largest = l;
        }
        // 如果右子樹大於根節點,則把右子樹設置最大值
        if (r < len && arrs[r] > arrs[largest]) {
            largest = r;
        }
        if (largest != i) {
            // 只交換最大那個分支
            swap(arrs, i, largest);
            // 遞歸修正換了節點那個分支
            heapify(arrs, largest, len);
        }
    }

    /**
     * 獲取左子節點的索引
     *
     * @param i
     * @return
     */
    public static int getLeft(int i) {
        return i * 2 + 1;
    }


    /**
     * 獲取右子節點的索引
     *
     * @param i
     * @return
     */
    public static int getRight(int i) {
        return i * 2 + 2;
    }

    /**
     * 獲取父親節點
     *
     * @param i
     * @return
     */
    public static int getParent(int i) {
        // 根節點
        if (i == 0) {
            return -1;
        }
        return (i - 1) / 2;
    }

    /**
     * 交換元素
     *
     * @param arrs
     * @param i
     * @param j
     */
    public static void swap(long[] arrs, int i, int j) {
        // 交換元素
        long tmp = arrs[i];
        arrs[i] = arrs[j];
        arrs[j] = tmp;
    }

}

4. 總結

構建堆的過程就是把最大值(最小值) 從最底層升到最上層, 根節點一定是最大值(最小值)

排序過程把首節點和尾節點互換,然後對之前的再次進行從上倒下構造堆

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章