堆排序 Heap Sort

堆排序 - Heap Sort

堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。java

堆的特徵

  • 堆的數據結構近似徹底二叉樹,即每一個節點存在兩個子節點
  • 當節點的值小於或等於父節點值,大於或等於子節點值稱爲大頂堆(也即根節點的值最大)
  • 當節點的值大於或等於父節點值,小於或等於子節點值稱爲小頂堆(也即根節點的值最小)
  • 若當前節點的索引爲 k , 那麼左子節點索引爲 2k + 1, 右子節點索引爲 2k + 2, 父節點索引爲 (k - 1) / 2

在本文中咱們以大頂堆爲例算法

堆的動做 - 上浮

上浮 : 是指在構建堆時,若節點的值大於父節點則將當前節點與父節點互相交換;直至該節點小於父節點時終止上浮(能夠理解爲一個新入職的員工能力出衆晉升更高的職位); 效果以下圖數組

代碼以下:數據結構

private void siftUp(int k) {
		// k == 0 時代表上浮到根節點,結束上浮操做
        while (k > 0) {
        	// 獲取父節點索引
            int parent = (k - 1) / 2;

            // 小於父節點時退出,結束上浮操做
            if (less(parent, k)) {
                break;
            }
            // 與父節點交換數據
            swap(parent, k);
            // 改變 k 的指向 繼續上浮
            k = parent;
        }
    }
複製代碼

堆的動做 - 下沉

下沉 : 是指構建堆的過程當中,若當前節點值小於子節點則將當前節點與子節點互相交換,直至該節點大於子節點時終止下沉(能夠理解爲一個leader能力平庸的時候被降職的過程,是否是有點很慘); 效果以下圖less

代碼以下:oop

private void siftDown (int k, int length) {
        // 獲取左子節點索引
        int childIndex = 2 * k + 1;
		// 判斷是否存在子節點
        while (childIndex < length) {
            // 判斷左右子節點 查找最大的子節點 
            if (childIndex + 1 < length && !less(childIndex, childIndex + 1)) {
                childIndex++;
            }

            // 若當前節點大於子節點 退出循環
            if (less(k, childIndex)) {
                break;
            }

            // 判斷當前節點是否小於子節點, 若小於執行交換
            swap(k, childIndex);
            // 改變 k 指向
            k = childIndex;

            childIndex = 2 * k + 1;
        }
    }
複製代碼

堆排序

那麼如何採用堆的數據結構,對一個無序的數組進行排序呢 ?this

  • 首先將無序數組構形成一個最大堆,此時根節點爲最大值
  • 將最後一個節點與根節點值交換,剔除最大值節點;
  • 將剩下節點從新執行構造堆
  • 循環執行第 2,3 兩步操做
無序數組構造堆

將無序數組構造堆,能夠採用上浮, 也能夠採用下沉的方式處理spa

如上圖所示,爲採用上浮的方式構建堆,其流程是依次從下標爲 0 開始對數組的每一個元素進行上浮操做,直至最後獲得一個有序的大頂堆。設計

如上圖所示,爲採用下沉的方式構建堆,其流程是依次從非葉子節點 開始對數組的每一個元素進行下沉操做,直至最後獲得一個有序的大頂堆。code

代碼以下:

for (int i = 0; i < array.length; i++) {
		// 上浮方式構建堆
        siftUp(i);
    }
複製代碼
// 由於堆是徹底二叉樹的特性, 因此下標小於等於 array.length / 2 的節點爲非葉子節點
// 採用下沉的方式 從下往上構建子堆
    for (int i = array.length / 2; i >= 0; i--) {
        siftDown(i, array.length);
    }
複製代碼

完成初始堆構造以後,剩下的工做就是重複進行如下邏輯(這個地方就不畫圖了):

  • 尾節點和根節點交換元素
  • 剔除尾節點,對餘下的元素進行子堆構造(構造堆的方式與初始堆同樣)

完整代碼以下 :

public class HeapSort {

    private int[] array;

    public HeapSort(int[] array) {
        this.array = array;
    }

    private boolean less (int i, int j) {
        return array[i] > array[j];
    }

    private void swap (int k, int j) {
        int temp = array[k];

        array[k] = array[j];
        array[j] = temp;
    }

    /** * 下沉操做 * * @param k */
    private void siftDown(int k, int length) {
        // loop
        // 判斷是否存在子節點
        int childIndex = 2 * k + 1;

        while (childIndex < length) {
            // 查找最大的子節點
            if (childIndex + 1 < length && !less(childIndex, childIndex + 1)) {
                childIndex++;
            }

            // 若當前節點大於子節點 退出循環
            if (less(k, childIndex)) {
                break;
            }

            // 判斷當前節點是否小於子節點, 若小於執行交換
            swap(k, childIndex);
            // 改變 k 指向
            k = childIndex;

            childIndex = 2 * k + 1;
        }
    }

    /** * 上浮操做 * * @param k */
    private void siftUp(int k) {
        while (k > 0) {
            int parent = (k - 1) / 2;

            // 小於父節點時退出
            if (less(parent, k)) {
                break;
            }
            // 與父節點交換數據
            swap(parent, k);
            // 改變 k 的指向
            k = parent;
        }
    }

    public void sort () {
        // 構造堆
        for (int i = 0; i < array.length; i++) {
            siftUp(i);
        }

        print();

        int n = array.length - 1;

        while (n > 0) {
            // 由於每次完成堆的構造後, 根節點爲最大(小)值節點
            // 將根節點與最後一個節點交換
            swap(0, n);

            for (int i = 0; i <= n - 1; i++) {
                // 排除有序的節點
                // 從新構造堆
                siftUp(i);
            }

            print();

            n--;
        }
    }

    private void sort1 () {
        // 構建堆
        // 由於堆是徹底二叉樹的特性, 因此下標小於等於 array.length / 2 的節點爲非葉子節點
        // 採用下沉的方式 從下往上構建子堆
        for (int i = array.length / 2; i >= 0; i--) {
            siftDown(i, array.length);
        }

        print();

        int n = array.length - 1;

        while (n > 0) {
            // 由於每次完成堆的構造後, 根節點爲最大(小)值節點
            // 將根節點與最後一個節點交換
            swap(0, n);

            for (int i = n / 2; i >= 0; i--) {
                // 排除有序的節點
                // 從新構造堆
                siftDown(i, n);
            }

            print();

            n--;
        }

    }
    private void print () {
        for (Integer num : array) {
            System.out.print(num);
            System.out.print(",");
        }
        System.out.println("");
    }

    public static void main(String[] args) {
        int[] array = {10, 40, 38, 20, 9, 15, 25, 30, 32};

        new HeapSort(array).sort();

        System.out.println("");

        new HeapSort(array).sort1();
    }
}

複製代碼

小結 : 堆排序在哪裏應用到了呢 ? 可參考優先隊列 PriorityQueue 的實現

相關文章
相關標籤/搜索