1. 程式人生 > >小頂堆排序(逆序)

小頂堆排序(逆序)

1. 問題描述:

對於一個亂序的整型陣列,使用小頂堆演算法對陣列進行排序,輸出排序之後的陣列

2. 小頂堆演算法

堆是一種經過排序的完全二叉樹,其中任一非葉子節點的資料值均不大於(或不小於)其左子節點和右子節點的值。

最大堆和最小堆是二叉堆的兩種形式

最大堆:根結點的鍵值是所有堆結點鍵值中最大者

最小堆:根結點的鍵值是所有堆結點鍵值中最小者

我們可以把陣列想象成一顆二叉樹,比如當陣列元素為7個的時候,元素值為9 7 8 2 6 7 9,那麼我們可以將其想象成樹的形式為

                             

先根據陣列下標逐層畫出每一層的元素,元素從左到右從上到下,我們可以根據一個公式來計算出非葉子節點的左子樹或者右子樹的下標

int left = 2 * n + 1;

int right = 2 * n + 2;

n為父節點的下標,left為左節點的下標,right為右節點的下標

小頂堆演算法從陣列下標為n  / 2 - 1的元素開始堆化,堆化的目的是為了維持每一個非葉子節點都是小於等於它的葉子節點的

像上面的7個元素的陣列我們應該從下標為2即元素值為8的元素開始網上進行堆化,使每一個非葉子節點都維持一個小頂堆

在堆化的過程中我們根據非葉子節點與子節點進行比較,如果非葉子節點大於了子節點那麼我們進行元素的交換,比如向上面的例子,從元素8開始堆化:

 

這樣我們就保證了小範圍的小頂堆,但是我們在往上堆化的過程中,我們在上一層可能在堆化的過程中有可能交換元素之後破壞了下面已經維持好的小頂堆,所以這個時候又要進行調整下層的小頂堆,這個時候我們採用的遞迴呼叫來調整被破壞的小頂堆

                     

像上面經過若干次的往上堆化我們得到了上面的小頂堆,這個時候我們需要把交換之後有影響的9再往下進行調整,所以使用遞迴的時候我們需要傳入交換的被交換的元素的下標,繼續遞歸向下進行調整,所以經過堆化之後那麼我們就得到了小頂堆了

                         

較小的元素都被放在了非葉子節點上去了,所以這有利於我們從第一個元素開始往下堆化,然後我們需要通過迴圈依次交換第一個元素與最後一個元素,交換之後最後一個元素就是最小的,那麼繼續從下標為零的元素9開始向下堆化,把最小的元素放在下標為零的位置,形成了下面的二叉樹,這樣下一次交換的就是下標為零和下標n - 2的元素,這樣便把第二小的元素放在了n - 2的位置

                 

通過迴圈向下堆化之後那麼我們最終可以得到逆序排列的陣列

3. 具體的程式碼如下:

import java.util.Random;
public class Main {
    public static void main(String[] args) {
        int arr[] = getRandom(10, 1, 20);
        print(arr);
        sort(arr);
        print(arr);
    }

    private static void print(int[] arr) {
        for(int i = 0; i < arr.length; i++){
            System.out.print(arr[i] + " ");
        }
        System.out.print("\n");
    }
    private static void sort(int[] arr) {
        int n = arr.length;
        //注意節點應該從n / 2 - 1開始
        for(int i = n / 2 - 1; i >= 0; i--){
            //傳入兩個引數是為了更好地交換元素
            //堆化之後那麼較小的元素都在上面的幾層
            makeMinHeap(arr, i, n);
        }
        
        for(int i = n - 1; i >= 0; i--){
            swap(arr, 0, i);
            makeMinHeap(arr, 0, i);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    private static void makeMinHeap(int[] arr, int node, int n) {
        //兩個葉子節點
        int left = 2 * node + 1;
        int right = 2 * node + 2;
        //噹噹前節點為葉子節點的時候那麼直接返回就可以了
        if(left >= n){
            return;
        }
        int min = left;
        if(right < n && arr[min] > arr[right]){
            //右子樹的節點比較小
            min = right;
        }
        //保持小頂堆比較父節點與最小的葉子節點的大小;
        if(arr[min] < arr[node]){
            swap(arr, min, node);
        }
        //繼續遞迴呼叫
        makeMinHeap(arr, min, n);
    }

    private static int[] getRandom(int len, int begin, int end) {
        int arr[] = new int[len];
        Random rm = new Random();
        for(int i = 0; i < len; i++){
            arr[i] = rm.nextInt(end - begin) + begin;
        }
        return arr;
    }
}