1. 程式人生 > >優先佇列或堆及堆排序介紹

優先佇列或堆及堆排序介紹

1 堆的基本概念

堆也叫優先佇列,堆是一種特殊的完全二叉樹資料結構,堆分為兩種,最大堆,最小堆。
最大堆:根節點大於左右兩個子節點的完全二叉樹
最小堆:根節點小於左右兩個子節點的完全二叉樹
堆可以用陣列來儲存,a[i]處存根節點,a[2*i] a[2*i + 1]分別存左子樹的根節點,右子樹的根節點。i從1開始

所以對於一個堆,結點i,其父結點為a[i/2],左子節點a[2*i],右子節點a[2*i + 1]

2 最大堆

根節點大於左右兩個子節點的完全二叉樹叫最大堆

1 堆的上浮
對於最大堆來說,如果某個結點比其父結點要大,那麼我們應該調整該結點的位置,從而使其滿足堆的結構。調整的思路很簡單,就是將與其父結點交換。這樣一直迴圈下去,直到所有結點滿足堆的結構。這個過程叫做堆的上浮

    /**
     * 堆的上浮,解決子節點比父結點大的問題
     * @param k 節點k上浮
     */
    private void swim(int k){
        //子節點比父結點大
        while (k > 1 && less(k/2,k)){
            //交換兩個節點
            exch(k/2,k);
            k = k/2;
        }
    }

時間複雜度O(logN)

2 堆的下沉
對於最大堆來說,如果我們發現某個結點小於子節點(同時小於兩個結點,或者小於一個結點)。這個時候我們也需要調整堆結構。調整思路就是將該結點與子節點中較大的結點做交換,遞迴下去。直到滿足堆的結構。

    /**
     * 堆的下沉 父結點小於子節點,將父節點與較大的子節點交換
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子節點 i = count;
        if ((i + 1 ) > count){
            if
(less(k,i)){ exch(k,i); } return; } //兩個孩子都大於父節點 if (less(k,i) || less(k,i + 1)){ if (less(i,i+1)){ exch(k,i+1); sink(i+1); }else { exch(k,i); sink(i); } } }

時間複雜度O(logN)

3 堆的插入
只要將插入的數放在陣列末尾,然後上浮這個結點即可

    /**
     * 插入一個元素,並上浮
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

時間複雜度O(logN)

4 堆的刪除
這裡刪除指的是刪除最大的元素,即刪除頂點元素。刪除思路很簡單:獲取陣列第一個元素,然後將第一個元素與最後一個元素交換,並下沉第一個元素即可。

    /**
     * 刪除最大的元素
     * @return
     */
    public T delMax(){
        T t = (T) pq[1];
        //刪除最大的,然後將末尾元素交換,並下沉
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

時間複雜度O(logN)

5最大堆實現
接下來我們看堆的完整程式碼:

**
 * @author Created by qiyei2015 on 2018/3/25.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description:
 */
public class BaseHeap<T extends Comparable<T>> {

    /**
     * 長度需N + 1
     */
    protected Comparable[] pq;

    /**
     * 容量 pq[1....N]
     */
    protected int N;
    /**
     * 堆中元素個數
     */
    protected int count;

    /**
     * 構造方法
     */
    public BaseHeap() {
        pq = new Comparable[0];
    }

    /**
     * 建立一個初始容量為max的堆
     * @param max
     */
    public BaseHeap(int max) {
        pq = new Comparable[max + 1];
        count = 0;
        N = max;
    }

    /**
     * 陣列建立
     * @param array
     */
    public BaseHeap(Comparable[] array) {
        this.pq = new Comparable[array.length + 1];
        System.arraycopy(array,0,pq,1,array.length);
        N = array.length;
        count = N;
    }

    /**
     * 時候為null
     * @return
     */
    public boolean isEmpty(){
        return count == 0;
    }

    /**
     * 返回堆中元素個數
     * @return
     */
    public int size(){
        return count;
    }

    /**
     * 比較 i < j
     * @param i
     * @param j
     * @return
     */
    protected boolean less(int i ,int j){
        return pq[i].compareTo(pq[j]) < 0;
    }

    /**
     * 交換兩個數
     * @param i
     * @param j
     */
    protected void exch(int i,int j){
        Comparable temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;
    }

}
/**
 * @author Created by qiyei2015 on 2018/3/25.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: 最大堆
 */
public class MaxPQ<T extends Comparable<T>> extends BaseHeap{

    /**
     * 無參構造方法
     */
    public MaxPQ() {
        super();
    }

    /**
     * 建立一個初始容量為max的堆
     * @param max
     */
    public MaxPQ(int max) {
        super(max);
    }

    /**
     * 用陣列建立堆,時間複雜度 O(N)
     * @param a
     */
    public MaxPQ(Comparable[] a){
        super(a);
        //最後一個父結點是 count / 2 [count/2...1]這個結點區間的結點都下沉,就是堆了
        for (int i = count/2; i >= 1 ; i--){
            sinkBetter(i);
        }
    }

    /**
     * 插入一個元素,並上浮
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

    /**
     * 刪除最大的元素
     * @return
     */
    public T delMax(){
        T t = (T) pq[1];
        //刪除最大的,然後將末尾元素交換,並下沉
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

    /**
     * 堆的上浮,解決子節點比父結點大的問題
     * @param k 節點k上浮
     */
    private void swim(int k){
        //子節點比父結點大
        while (k > 1 && less(k/2,k)){
            //交換兩個節點
            exch(k/2,k);
            k = k/2;
        }
    }

    /**
     * 堆的上浮,解決子節點比父結點大的問題,少交換,優化堆的上浮過程
     * @param k 節點k上浮
     */
    private void swimBetter(int k){
        Comparable temp =  pq[k];
        //子節點比父結點大
        while (k > 1 && less(k/2,k)){
            //父結點移到子節點 子節點暫存 不用每次都去新建一個臨時變數來交換
            pq[k] = pq[k/2];
            pq[k/2] = temp;
            k = k/2;
        }
    }


    /**
     * 堆的下沉 父結點小於子節點,將父節點與較大的子節點交換
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子節點 i = count;
        if ((i + 1 ) > count){
            if (less(k,i)){
                exch(k,i);
            }
            return;
        }
        //兩個孩子都大於父節點
        if (less(k,i) || less(k,i + 1)){
            if (less(i,i+1)){
                exch(k,i+1);
                sink(i+1);
            }else {
                exch(k,i);
                sink(i);
            }
        }

//        //判斷有左孩子,有孩子就行
//        while (2 * k <= N){
//            int j = 2 * k; //此輪迴圈中 k 與j交換
//            if ((j +1) <= N && less(j,j+1)){
//                j++; //更新為右孩子
//            }
//            //父結點大於子節點
//            if (!less(k,j)){
//                break;
//            }
//            exch(k,j);
//            k = j; //更新k的位置
//        }

    }


    /**
     * 堆的下沉 父結點小於子節點,將父節點與較大的子節點交換
     * @param k
     */
    private void sinkBetter(int k) {
        Comparable temp = pq[k];
        //判斷有左孩子,有孩子就行
        while (2 * k <= count) {
            int j = 2 * k; //此輪迴圈中 k 與j交換
            if ((j + 1) <= count && less(j, j + 1)) {
                j++; //更新為右孩子
            }
            //父結點大於子節點
            if (!less(k, j)) {
                break;
            }

            //將子節點移到父結點,父結點移到子節點 不用每次都去新建一個臨時變數來交換
            pq[k] = pq[j];
            pq[j] = temp;
            k = j; //更新k的位置
        }
    }
}

3 最小堆

與最大堆類似,我們只需要在堆的上浮和下沉改變一下條件,即可建立最小堆

1 最小堆上浮
結點比父節點小,該結點與父節點交換

    /**
     * 堆的上浮,如果子節點比父節點小,就交換
     * @param k 節點k上浮
     */
    private void swim(int k){
        //子節點比父結點大
        while (k > 1 && less(k,k/2)){
            //交換兩個節點
            exch(k/2,k);
            k = k/2;
        }
    }

時間複雜度O(logN)

2 最小堆下沉
父結點大於子節點,該結點與子結點中較小的節點交換

    /**
     * 堆的下沉 父結點大於子節點,將父節點與較小的子節點交換,並下沉子節點
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子節點 i = count;
        if ((i+1) > count){
            if (less(i,k)){
                exch(k,i);
            }
            return;
        }

        //父節點與較小的子節點交換
        if (less(i,k) || less(i+1,k)){
            //將父節點與較小的節點交換
            if (less(i,i+1)){
                exch(k,i);
                sink(i);
            }else {
                exch(k,i+1);
                sink(i+1);
            }
        }
    }

時間複雜度O(logN)

3 最小堆實現
完整最小堆程式碼:

package com.qiyei.heap;

/**
 * @author Created by qiyei2015 on 2018/4/16.
 * @version: 1.0
 * @email: 1273482124@qq.com
 * @description: 最小堆
 */
public class MinPQ<T extends Comparable<T>> extends BaseHeap {
    /**
     * 無參構造方法
     */
    public MinPQ() {
        super();
    }

    /**
     * 建立一個初始容量為max的堆
     * @param n
     */
    public MinPQ(int n) {
        super(n);
    }

    /**
     * 用陣列建立堆,時間複雜度 O(N)
     * @param a
     */
    public MinPQ(Comparable[] a){
        super(a);
        for (int i = count/2; i >= 1 ; i--){
            sinkBetter(i);
        }
    }

    /**
     * 插入一個元素
     * @param t
     */
    public void insert(T t){
        pq[++count] = t;
        swimBetter(count);
    }

    /**
     * 刪除最小的元素
     * @return
     */
    public T delMin(){
        T t = (T) pq[1];
        exch(1,count);
        pq[count] = null;
        count--;
        sinkBetter(1);
        return t;
    }

    /**
     * 堆的上浮,如果子節點比父節點小,就交換
     * @param k 節點k上浮
     */
    private void swim(int k){
        //子節點比父結點大
        while (k > 1 && less(k,k/2)){
            //交換兩個節點
            exch(k/2,k);
            k = k/2;
        }
    }

    /**
     * 堆的上浮,如果子節點比父節點小,就交換。優化堆的上浮過程
     * @param k 節點k上浮
     */
    private void swimBetter(int k){
        Comparable temp =  pq[k];
        //子節點比父結點大
        while (k > 1 && less(k,k/2)){
            //父結點移到子節點 子節點暫存 不用每次都去新建一個臨時變數來交換
            pq[k] = pq[k/2];
            pq[k/2] = temp;
            k = k/2;
        }
    }


    /**
     * 堆的下沉 父結點大於子節點,將父節點與較小的子節點交換,並下沉子節點
     * @param k
     */
    private void sink(int k){
        if (k > count){
            return;
        }
        int i = 2 * k;
        if (i > count ){
            return;
        }

        //只有左子節點 i = count;
        if ((i+1) > count){
            if (less(i,k)){
                exch(k,i);
            }
            return;
        }

        //父節點與較小的子節點交換
        if (less(i,k) || less(i+1,k)){
            //將父節點與較小的節點交換
            if (less(i,i+1)){
                exch(k,i);
                sink(i);
            }else {
                exch(k,i+1);
                sink(i+1);
            }
        }
    }


    /**
     * 堆的下沉 父結點大於子節點,將父節點與較小的子節點交換
     * @param k
     */
    private void sinkBetter(int k) {
        Comparable temp = pq[k];
        //判斷有左孩子,有孩子就行
        while (2 * k <= count) {
            int j = 2 * k; //此輪迴圈中 k 與j交換
            if ((j + 1) <= count && less(j+1, j)) {
                j++; //更新為右孩子
            }
            //父結點大於子節點
            if (!less(j, k)) {
                break;
            }

            //將子節點移到父結點,父結點移到子節點 不用每次都去新建一個臨時變數來交換
            pq[k] = pq[j];
            pq[j] = temp;
            k = j; //更新k的位置
        }
    }
}

4 堆排序

有了最大最小堆,堆排序變得非常簡單。首先根據陣列構造堆,然後依次取堆頂元素即可,對於最大堆,需要把倒序賦值陣列。
堆排序的時間複雜度是O(nlogn)

    /**
     * 堆排序
     * @param array
     */
    @Override
    public void sort(Comparable[] array) {
        //時間複雜度O(n)
        MaxPQ maxPQ = new MaxPQ(array);
        for (int i = array.length - 1 ; i >= 0 ; i--){
            //時間複雜度O(logn)
            array[i] = maxPQ.delMax();
        }
    }

最後:
原始碼github https://github.com/qiyei2015/Algorithms heap部分