堆排序

實現(xiàn)須知
  • 最大堆的性質(zhì)
    • 堆是特殊的完全二叉樹
    • 所有父節(jié)點的值都比其子節(jié)點的值大
    • 堆頂是最大元素
  • 堆化一個節(jié)點
    • 使該父節(jié)點與其子節(jié)點中值最大的節(jié)點位于該父節(jié)點的位置
    • 也即如果有個子節(jié)點是這三個節(jié)點(一父兩子)中最大的,則交換該節(jié)點與父節(jié)點的位置
    • 上述步驟(交換兩個節(jié)點的位置)可能導致新的子節(jié)點(原來的父節(jié)點被交換下去變成新的子節(jié)點)作為父節(jié)點時不滿足最大堆的條件,于是再以它為父節(jié)點重復上述步驟,直至到達樹的末端
  • 堆化一棵完全二叉樹
    • 從最后一個父節(jié)點開始堆化,然后堆化倒數(shù)第二個父節(jié)點,倒數(shù)第三個………直到堆化堆頂節(jié)點
  • 本文實現(xiàn)方法是利用最大堆的性質(zhì)"升序"排序,并且基于數(shù)組以及索引的,所以并沒有真正的完全二叉樹類和節(jié)點類
算法思路
  • 將要排序的序列看成二叉樹并堆化
  • 將堆頂元素與最后一個元素交換,此時最大元素就換到了最后
  • 將堆頂節(jié)點堆化
  • 重復步驟:
    • 交換
      要注意最大的元素已經(jīng)被交換到最后,所以每次交換的時候,"最后一個元素"是上一次的前一個
    • 堆化堆頂節(jié)點

算法實現(xiàn)

具體方法
  • 將交換數(shù)組元素的方法void swap(int[], int, int)進行封裝
    /**
     * 交換數(shù)組中的兩個數(shù)
     *
     * @param nums 要交換值的數(shù)組
     * @param i    第一個索引
     * @param j    第二個索引
     * @return void
     */
    public static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
  • 堆化一個節(jié)點void nodeHeapify(int[] tree, int index, int N)
    參數(shù)N是當前需要堆化的元素個數(shù)
    • 記錄最大值索引(假定父節(jié)點是最大值),用于遞歸
    • 記錄左右子節(jié)點索引
      • 左子節(jié)點索引 = (父節(jié)點索引 * 2) + 1leftIndex = (index * 2) + 1
      • 右子節(jié)點索引 = (父節(jié)點索引 * 2) + 2rightIndex = (index * 2) + 2
    • 分別判斷左右子節(jié)點索引是否越界,再更新最大值索引
    • 如果最大值索引不是父節(jié)點索引,則交換父節(jié)點索引與最大值索引的值,然后遞歸堆化nodeHeapify(tree, maxIndex, N)
    /**
     * 使某個節(jié)點與他的子節(jié)點符合最大堆的性質(zhì)
     *
     * @param tree  要進行排序的樹(以數(shù)組形式)
     * @param index 要堆化的節(jié)點索引
     * @param N     樹的節(jié)點數(shù)
     * @return void
     */
    public static void nodeHeapify(int[] tree, int index, int N) {
        // 找出index索引節(jié)點與它的子節(jié)點中最大的節(jié)點
        // 記錄最大值節(jié)點的索引, 用于遞歸
        int maxIndex = index;
        // 找出左右子節(jié)點的索引
        int leftIndex = (index * 2) + 1;
        int rightIndex = (index * 2) + 2;
        // (索引不越界情況下)如果左子節(jié)點值大于最大值
        if (leftIndex < N && tree[leftIndex] > tree[maxIndex]) {
            // 更新最大值索引
            maxIndex = leftIndex;
        }
        // (索引不越界情況下)如果右子節(jié)點值大于最大值
        if (rightIndex < N && tree[rightIndex] > tree[maxIndex]) {
            // 更新最大值索引
            maxIndex = rightIndex;
        }
        // 如果父節(jié)點不是三個節(jié)點中最大的
        if (index != maxIndex) {
            // 則交換最大值索引與父節(jié)點索引的值(此時三個節(jié)點以滿足最大堆的特性)
            swap(tree, index, maxIndex);
            // 但是剛才與父節(jié)點交換的節(jié)點, 可能它與它的子節(jié)點因為這次交換而不滿足最大堆特性
            // 遞歸, 對剛才被交換的最大值索引的節(jié)點進行堆化
            nodeHeapify(tree, maxIndex, N);
        }
    }
  • 堆化一棵完全二叉樹
    • 從最后一個父節(jié)點lastParentIndex = (N - 1 - 1) / 2開始堆化
      • 最后一個節(jié)點索引N - 1
      • 節(jié)點的父節(jié)點索引(index - 1) / 2(舍去余數(shù))
    • 然后堆化倒數(shù)第二個節(jié)點,依次往上走for (int i = lastParentIndex; i >= 0; i--)
    /**
     * 將整個完全二叉樹堆化(每個節(jié)點堆化)
     *
     * @param tree 要堆化的樹
     * @param N    樹的節(jié)點數(shù)
     * @return void
     */
    public static void treeHeapify(int[] tree, int N) {
        // 從最后一個父節(jié)點開始, 往前走, 堆化每個父節(jié)點
        int lastParentIndex = (N - 1 - 1) / 2;
        for (int i = lastParentIndex; i >= 0; i--) {
            nodeHeapify(tree, i, N);
        }
    }
  • 堆排序主體void heapSort(int[], int)
    • 首先堆化整棵樹
    • 循環(huán)執(zhí)行for (int i = N - 1; i > 0; i--)交換堆頂和最后一個元素,堆化堆頂元素,i == 0時所有元素已經(jīng)排序好
    • 注意:每次最后一個元素是上次一次最后一個元素的前一個,所以每次堆化堆頂元素的時候,把堆的大小當作較少了1,于是傳進去的參數(shù)N = i(iN - 1遞減到0)
    /**
     * 堆排序: 將樹堆化之后, 依次取出堆頂?shù)闹? 重新堆化, 直到取出所有值
     *
     * @param tree 要排序的數(shù)組
     * @param N    樹的節(jié)點數(shù)
     * @return int[] result 排序后的數(shù)組
     */
    public static void heapSort(int[] tree, int N) {
        // 將樹堆化
        treeHeapify(tree, N);
        // 依次取出堆頂(將最后一個節(jié)點與堆頂?shù)闹祵Q), 堆化堆頂節(jié)點
        for (int i = N - 1; i > 0; i--) {
            // 堆頂是最大值, 將堆頂與最后一個節(jié)點值交換
            // 每循環(huán)一次, 雖然數(shù)組大小不變, 但因為每次都取出堆頂所以堆的"大小" - 1
            swap(tree, 0, i);
            // 堆化堆頂節(jié)點(注意每次循環(huán)堆的的大小N - 1, 所以參數(shù)N = i)
            nodeHeapify(tree, 0, i);
        }
    }
測試
    public static void main(String[] args) {
        int[] nums = {1, 4, 7, 5, 2, 6, 3, 8, 9, 1, 4, 7, 8, 5, 2, 3, 1, 4};
        System.out.println(Arrays.toString(nums));
        heapSort(nums, nums.length);
        System.out.println(Arrays.toString(nums));
    }

排序前:[ 1, 4, 7, 5, 2, 6, 3, 8, 9, 1, 4, 7, 8, 5, 2, 3, 1, 4 ]
排序后:[ 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 7, 7, 8, 8, 9 ]

完整代碼
public class HeapSortDemo {
    public static void main(String[] args) {
        int[] nums = {1, 4, 7, 5, 2, 6, 3, 8, 9, 1, 4, 7, 8, 5, 2, 3, 1, 4};
        System.out.println(Arrays.toString(nums));
        heapSort(nums, nums.length);
        System.out.println(Arrays.toString(nums));
    }

    /**
     * 交換數(shù)組中的兩個數(shù)
     *
     * @param nums 要交換值的數(shù)組
     * @param i    第一個索引
     * @param j    第二個索引
     * @return void
     */
    public static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    /**
     * 使某個節(jié)點與他的子節(jié)點符合最大堆的性質(zhì)
     *
     * @param tree  要進行排序的樹(以數(shù)組形式)
     * @param index 要堆化的節(jié)點索引
     * @param N     樹的節(jié)點數(shù)
     * @return void
     */
    public static void nodeHeapify(int[] tree, int index, int N) {
        // 找出index索引節(jié)點與它的子節(jié)點中最大的節(jié)點
        // 記錄最大值節(jié)點的索引, 用于遞歸
        int maxIndex = index;
        // 找出左右子節(jié)點的索引
        int leftIndex = (index * 2) + 1;
        int rightIndex = (index * 2) + 2;
        // (索引不越界情況下)如果左子節(jié)點值大于最大值
        if (leftIndex < N && tree[leftIndex] > tree[maxIndex]) {
            // 更新最大值索引
            maxIndex = leftIndex;
        }
        // (索引不越界情況下)如果右子節(jié)點值大于最大值
        if (rightIndex < N && tree[rightIndex] > tree[maxIndex]) {
            // 更新最大值索引
            maxIndex = rightIndex;
        }
        // 如果父節(jié)點不是三個節(jié)點中最大的
        if (index != maxIndex) {
            // 則交換最大值索引與父節(jié)點索引的值(此時三個節(jié)點以滿足最大堆的特性)
            swap(tree, index, maxIndex);
            // 但是剛才與父節(jié)點交換的節(jié)點, 可能它與它的子節(jié)點因為這次交換而不滿足最大堆特性
            // 遞歸, 對剛才被交換的最大值索引的節(jié)點進行堆化
            nodeHeapify(tree, maxIndex, N);
        }
    }

    /**
     * 將整個完全二叉樹堆化(每個節(jié)點堆化)
     *
     * @param tree 要堆化的樹
     * @param N    樹的節(jié)點數(shù)
     * @return void
     */
    public static void treeHeapify(int[] tree, int N) {
        // 從最后一個父節(jié)點開始, 往前走, 堆化每個父節(jié)點
        int lastParentIndex = (N - 1 - 1) / 2;
        for (int i = lastParentIndex; i >= 0; i--) {
            nodeHeapify(tree, i, N);
        }
    }

    /**
     * 堆排序: 將樹堆化之后, 依次取出堆頂?shù)闹? 重新堆化, 直到取出所有值
     *
     * @param tree 要排序的數(shù)組
     * @param N    樹的節(jié)點數(shù)
     * @return int[] result 排序后的數(shù)組
     */
    public static void heapSort(int[] tree, int N) {
        // 將樹堆化
        treeHeapify(tree, N);
        // 依次取出堆頂(將最后一個節(jié)點與堆頂?shù)闹祵Q), 堆化堆頂節(jié)點
        for (int i = N - 1; i > 0; i--) {
            // 堆頂是最大值, 將堆頂與最后一個節(jié)點值交換
            // 每循環(huán)一次, 雖然數(shù)組大小不變, 但因為每次都取出堆頂所以堆的"大小" - 1
            swap(tree, 0, i);
            // 堆化堆頂節(jié)點(注意每次循環(huán)堆的的大小N - 1, 所以參數(shù)N = i)
            nodeHeapify(tree, 0, i);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容