1. 程式人生 > >常見排序之堆排序

常見排序之堆排序

文章目錄


前言

排序演算法的成本模型計算的是比較和交換的次數。less()方法對元素進行比較,exch()方法將元素交換位置。

private static boolean less(Comparable v, Comparable w) {
   return (v.compareTo(w) < 0);
}

private static void
exch(Comparable[] a, int i, int j) { Comparable swap = a[i]; a[i] = a[j]; a[j] = swap; }

堆的定義

堆的某個節點的值總是大於等於子節點的值,並且堆是一顆完全二叉樹。當這棵樹的每個結點都大於等於它的兩個子節點時,它被稱為堆有序。

堆可以用陣列來表示,這是因為堆是完全二叉樹,而完全二叉樹很容易就儲存在陣列中。位置 k 的節點的父節點位置為 k/2,而它的兩個子節點的位置分別為 2k 和 2k+1。

堆

上浮

在堆中,當一個節點比父節點大,那麼需要交換這個兩個節點。交換後還可能比它新的父節點大,因此需要不斷地進行比較和交換操作,把這種操作稱為上浮。

上浮

實現如下:

private void swim(int k) {
   while (k > 1 && less(k/2, k)) {
      exch(k, k/2);
      k = k/2;
   }
}

下沉

在堆中,當一個節點比子節點小,需要不斷地向下進行比較和交換操作,把這種操作稱為下沉。一個節點如果有兩個子節點,應當與兩個子節點中最大那個節點進行交換。

下沉

實現如下:

private void sink(int k) {
   while (2*k <= N) {
      int j = 2*k;
      if
(j < N && less(j, j+1)) j++; if (!less(k, j)) break; exch(k, j); k = j; } }

堆排序

堆排序可以分為兩個階段。在堆的構造階段中,我們將原始陣列重新組織安排進一個堆中;然後在下沉排序階段,我們從堆中按遞減順序取出所有元素並得到排序結果。

堆排序

堆的構造

無序陣列建立堆最直接的方法是從左到右遍歷陣列進行上浮操作。一個更高效的方法是從右至左進行下沉操作,如果一個節點的兩個節點都已經是堆有序,那麼進行下沉操作可以使得這個節點為根節點的堆有序。葉子節點不需要進行下沉操作,可以忽略葉子節點的元素,因此只需要遍歷一半的元素即可。

下沉排序

堆排序的主要工作都是在這一階段完成的。這裡我們將堆中的最大元素刪除,然後放入堆縮小後陣列中空出的位置。

public class Heap {
    public static void sort(Comparable[] pq) {
        int n = pq.length;
        for (int k = n/2; k >= 1; k--)
            sink(pq, k, n);
        while (n > 1) {
            exch(pq, 1, n--);
            sink(pq, 1, n);
        }
    }

    private static void sink(Comparable[] pq, int k, int n) {
        while (2*k <= n) {
            int j = 2*k;
            if (j < n && less(pq, j, j+1)) j++;
            if (!less(pq, k, j)) break;
            exch(pq, k, j);
            k = j;
        }
    }

    private static boolean less(Comparable[] pq, int i, int j) {
        return pq[i-1].compareTo(pq[j-1]) < 0;
    }

    private static void exch(Object[] pq, int i, int j) {
        Object swap = pq[i-1];
        pq[i-1] = pq[j-1];
        pq[j-1] = swap;
    }
}

複雜度分析

一個堆的高度為 logN,因此在堆中插入元素和刪除最大元素的複雜度都為 logN。

對於堆排序,由於要對 N 個節點進行下沉操作,因此複雜度為 NlogN。

現代系統的許多應用很少使用它,因為它無法利用快取。陣列元素很少和相鄰的其它元素進行比較,因此無法利用區域性性原理,快取未命中的次數很高。

  • 最壞時間複雜度 О(nlogn)
  • 最優時間複雜度 O(nlogn)
  • 平均時間複雜度 O(nlogn)
  • 空間複雜度 O(1)
  • 不穩定

參考資料