堆排序與優先佇列

YZcxy發表於2017-12-05

說到堆就必須要說二叉樹,二叉樹指每個節點最多隻能包含兩個子節點的樹。二叉樹常用的實現為二叉搜尋樹(BinarySearchTree)和二叉堆(BinaryHeap)

這裡不再對樹的概念進行贅述,有需求的自行google,二叉堆其實對應著一棵完全二叉樹,最後一層除外。因此使得一個堆可以利用陣列來儲存,二叉堆又分為大根堆和小根堆,下圖展示了一個大根堆與陣列的對應。

image

因此可以很簡單的得到堆節點所對應的陣列。 :banana:

  • 父節點parent = index / 2
  • 左節點left = index * 2
  • 右節點right = index * 2 + 1

堆排序

堆排序就是把最大堆堆頂的最大數取出,將剩餘的堆繼續調整為最大堆,再次將堆頂的最大數取出,這個過程持續到剩餘數只有一個時結束。在堆中定義以下幾種操作:

  • 最大堆調整(Max-Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點。
  • 建立最大堆(Build-Max-Heap):將堆所有資料重新排序,使其成為最大堆。
  • 堆排序(Heap-Sort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算。

通俗的解釋就是: 最大堆調整(Max-Heapify)其實就是一次 <下沉> 的過程,建立最大堆(Build-Max-Heap)就是 <迴圈> 每個節點進行 最大堆調整,堆排序(Heap-Sort)就是每次 建立完最大堆 之後需要將最大元素 <分離>

一句話概括:迴圈樹的每一個節點進行下沉,然後分離,繼續迴圈。 :tomato:

下圖展示了一次下沉的過程:

picture

優先佇列

普通的佇列滿足先進先出,而優先佇列滿足每次出佇列的都是優先順序最高的元素。 :strawberry:

常見的優先佇列有三種實現方式:

  • 有序陣列(add時新增到排序的位置,poll時候移除隊尾元素)
  • 無序陣列(add時直接新增到隊尾,poll時查詢優先元素)
  • 二叉堆(add時上浮,poll時下沉)

image

優先佇列與堆排序的關係

因為優先佇列每次poll只需要最大優先順序的元素,所以不需要維持整棵二叉堆的有序,只需要維持根節點滿足最大優先順序即可。所以只需要對根節點進行一次堆排序的最大堆調整(Max-Heapify)即可。

Java優先佇列原始碼解析

    

    //add方法也就是直接呼叫offer方法
    public boolean offer(E e) {
        //不允許插入null
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        //容量不夠則進行擴容
        if (i >= queue.length)
            grow(i + 1);
        siftUp(i, e);//這裡呼叫了上浮
        size = i + 1;
        return true;
    }
    
    //上浮分為兩種情況,判斷是否設定了comparator
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

    //我們只分析這種沒有設定comparator的方法,另一種類比
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            //找到父節點
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            //如果當前節點大於父節點則上浮結束
            if (key.compareTo((E) e) >= 0)
                break;
            //否則,將父節點下沉
            queue[k] = e;
            k = parent;
        }
        //將節點賦值到正確的上浮位置
        queue[k] = key;
    }
    
    //poll方法
    public E poll() {
        //如果沒有元素,則返回null
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        //返回根節點
        E result = (E) queue[0];
        E x = (E) queue[s];
        //將隊尾元素移到根節點,並進行下沉
        queue[s] = null;
        //佇列中不止一個元素,則進行下沉
        if (s != 0)
            siftDown(0, x);
        return result;
    }
    
    //下沉方法的實現與上浮類似,就不贅述了。

複製程式碼

相關文章