堆,堆排序

很久沒寫博客了 還是抽時間寫一寫~

堆用來做優先隊列 插入與刪除平均時間複雜度都爲O(n),以鏈表實現插入是O(1),刪除是O(n),數組實現反之

JUC包就有優先隊列的實現(後面深入併發後再補充)

判斷:葉子節點爲左節點時是完全二叉樹,葉子節點爲右節點時,不連續緊密排列,非完全二叉樹

堆的定義:

堆是一顆完全二叉樹。堆的基本要求是堆中所有結點的值必須大於(或小於)其孩子結點的值。

完全二叉樹:

若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層有葉子結點,並且葉子結點都是從左到右依次排布。

堆是數據結構(以大根堆爲例)

public class Heap {
    // 容量
    private int capbility;
    // 元素大小
    private int count;
    // 元素數組
    private int[] data;

    Heap(int capbility) {
        this.data = new int[capbility+1];
        this.capbility = capbility;
        this.count = 0;
    }

}

注意 這裏是capbility+1; 因爲我們堆結點是從索引1開始,從1到capbility+1,data[0]是空

介紹兩個性質

獲取父結點 i/2

獲取子結點 2*i 和 2*i+1

(如果是capbility則只是計算不同,獲取父結點 (i-1)/2,子結點 2*i +1和 2*i+2 )

然後是插入操作

插入到是插入到最後的結點位置,容量++,爲了保持堆的性質,開始上升shiftUp操作,不斷與父結點比較,如果大於父結點則交換,到一個節點的時候或者滿足堆的性質

public void insert(int item) {
        // FIXME 可改爲擴容操作
        assert(count + 1 <= capbility);
        count++;
        data[count] = item;
        shiftUp(count);
    }

    private void shiftUp(int k) {
        while (k > 1 && data[k] > data[k/2])
        {
            // 與父結點交換
            SortHelper.swap(data,k,k/2);
            // 繼續比較
            k = k/2;
        }
    }

刪除操作

把data[1](ps:data[0]爲空)刪除,容量--,爲了保持堆的性質,把data[count]換到data[1],開始shiftDown操作,不斷與子節點比較,與子節點中大的那個交換位置,到無子節點或者已經滿足大於堆的性質

public int delete(){
        assert( count > 0 );
        int item = data[1];
        data[1] = data[count];
        count --;
        shiftDown(1);
        return item;
    }

    private void shiftDown(int k) {
        // 如果有左節點
        while (2*k <= count){
            int j = 2*k;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值
            if (j+1 <= count && data[j] < data[j+1]) {
                j = j+1;
            }
            // 與子節點最大的進行比較 如果大於子節點 就直接跳出
            if( data[k] >= data[j] ) break;
            // 交換位置和索引繼續比較
            SortHelper.swap(data,k,j);
            k = j;
        }
    }

測試結果

 

堆排序

由上面得出結論 我們按照順序每次把刪除的元素就保存就是堆排序 是不是很簡單~

public static void main(String[] args) {
        int n = 10;
        Heap heap = new Heap(n);
        for (int i = 0; i < 10; i++) {
            heap.insert((i+1)*2);
        }
        System.out.println("構建的堆:"+Arrays.toString(heap.getData()));
        int[] arr = new int[n];
        for (int i = 0; i < n ; i++) {
            arr[i] = heap.delete();
        }
        System.out.println("排序後的數組:"+Arrays.toString(arr));
    }

 構建堆優化

其實構建堆不用每次都加一個元素就shiftUp操作

葉子節點一開始就一個 無需進行任何操作,所以我們從第一個非葉子節點進行,也就是22(索引5)和之前的元素進行shiftDown操作 ,

獲取第一個非葉子節點:獲取最後一個葉子節點(data[count])的父結點(data[count/2])

public void heapify(int[] arr, int n) {
        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;
        for (int i = n/2; i >= 1; i--) {
            shiftDown(i);
        }
    }

注意我們這裏是>= 1,因爲我們從索引1開始計算

 

原地排序(不需要藉助輔助空間) 

把最大的data[0]一直往數組最後放,放完該排序的數組大小-1,再重複進行這個操作

public static void localSort(int[] arr,int n) {
        heapify(arr, n);
        for (int i = 0; i < n ; i++) {
            SortHelper.swap(arr,0,n-(i+1));
            __shiftDown(arr, n-(i+1), 0);
        }
    }

代碼優化