算法概論-堆排序

在看搜索引擎做查詢結果排序的用到了堆排序,特來複習一下。
那麼在深入堆排序之前先來列舉一下常見的排序方法,
Insertion sort ,最簡單直觀的排序方法,時間複雜度最壞O(n2 ),in place(Recall that a sorting algorithm sorts in place if only a constant number of elements of the input array are ever stored outside the array.)就是說除了輸入數組,僅還需耗費常數大小的空間, 這裏對於insert sorting,應該只在交換element時,需要一個element的額外的暫存空間。此方法適用於size很小的輸入。
Merge sort ,基於分治的一種排序算法,時間複雜度O(nlgn),但不是in place的,明顯merge的時候需要較多的額外空間。
Heap sort ,我們下面要介紹的,時間複雜度O(nlgn), 而且是in place的。
Quick sort , 快排,最差時間複雜度O(n2 ),平均的時間複雜度爲O(nlgn),但是據說在實際引用時比堆排序高效。

下面開始介紹heap sort,
那麼堆排序當然核心就是堆這個數據結構,堆是個完全二叉樹,而且每個節點都比左右子節點大(或小),因爲堆分爲max堆和min堆。
完全二叉樹有個非常高效的存儲方法,就是數組,一般的樹都要用鏈表去存儲。
對於heap sort的輸入數組,如A[16,4,10,14,7,9,3,2,8,1],要進行堆排序,首先要建堆,建堆可以分爲兩步:
將輸入數組抽象成完全二叉樹
建堆BUILD-MAX-HEAP
那麼上面的輸入數組可以抽象成如下的二叉樹,
            16
         4     10
       14   7  9   3
      2  8 1
那麼一般你必須去記錄這個樹結構,對吧,一般用鏈表來記錄節點,節點的左右子節點的指針,這樣就需要耗費比輸入數組多幾倍的空間,這樣就無法in place了。
妙就妙在,你根據輸入數組依次建立的這個完全二叉樹,不用任何額外的空間去記錄。這就得益於完全二叉樹本身就是可以用數組存儲的,這種數據結構是非常高效的。
對於數組中任一節點,你想知道它在完全二叉樹中的parent,left,right,非常容易:
PARENT (i)
   return i/2

LEFT (i)
   return 2i

RIGHT (i)
   return 2i + 1
那麼現在對於輸入數組,已經抽象爲完全二叉樹了,那就要開始建堆,
先來學習一個重要的堆操作MAX-HEAPIFY
MAX-HEAPIFY (A, i)
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i],A[largest]
10         MAX-HEAPIFY(A, largest)
這個函數就是對數組A中的第i個節點進行heapify操作
其實比較簡單,1~7就是比較找出,i節點和左右子節點中,哪個最大
8~10,如果最大的不是i,那就把最大節點的和i節點交換,然後遞歸對從最大節點位置開始繼續進行heapify
顯而易見,對於n個節點的完全二叉樹,高爲lgn,對每個節點的heapify操作是常數級的,所以這個操作的時間複雜度就是lgn
那麼有了heapify操作,建堆的算法很簡單的,
BUILD-MAX-HEAP (A)
1  heap-size[A] ← length[A]
2  for i ← length[A]/2 downto 1
3       do MAX-HEAPIFY(A, i)

說白了,就是對i從length[A]/2到1的節點進行heapify操作。所以這個操作的時間複雜度上限咋一看應該是nlgn,其實比這個小的多,約等於2n,就是說建堆的時間複雜度是O(n),能夠在線性時間內完成,這個是很高效的。
這個算法的依據是the elements in the subarray A[(n/2+1) ‥ n] are all leaves of the tree,所以我們只需要對所有非葉節點進行heapify操作就ok了
折騰半天堆建好了,怎麼堆排序了,光從堆是得不到一個有序序列的。
HEAPSORT (A)
1 BUILD-MAX-HEAP(A)
2 for i ← length[A] downto 2
3    do exchange A[1],A[i]
4       heap-size[A] ← heap-size[A] - 1
5       MAX-HEAPIFY(A, 1)
原理很簡單,從堆我們只能知道最大的那個,那麼就把最大的那個去掉,然後heapify找到第二大的,依次下去。
實現也很巧妙,沒有用到額外的存儲空間,把堆頂放到堆尾,然後堆size-1
這個算法的時間複雜度也是nlgn

Python版


堆排序介紹完了,有什麼應用
我看到的在搜索引擎在生成查詢結果時,需要對N個候選集進行排序並取前r個作爲查詢結果,這時r<<N
這時用堆排序比較經濟,首先生成堆,然後排序的時候只要做r次heapify,然後後面的就可以不管了,省了很多時間。

書上介紹的典型應用是Priority queues
說了堆排序是個非常好的排序算法,但是在實際應用中了還是輸給了快排,所以別人都用快排了。但是heap這個數據結構的應用是很廣的。
比如這個典型應用Priority queues
queue就是先進先出,那麼Priority queues有了priority,複雜一點了,priority大的先出,這個可以用於比如cpu的task,job調度。
這個priority queue用堆實現就很合適了,下面就是定義了需要的一些操作,
HEAP-MAXIMUM (A)
1 return A[1]

HEAP-EXTRACT-MAX (A)
1 if heap-size[A] < 1
2   then error "heap underflow"
3 max ← A[1]
4 A[1] ← A[heap-size[A]]
5 heap-size[A] ← heap-size[A] - 1
6 MAX-HEAPIFY(A, 1)
7 return max

HEAP-INCREASE-KEY (A, i, key)
1 if key < A[i]
2   then error "new key is smaller than current key"
3 A[i] ← key
4 while i > 1 and A[PARENT(i)] < A[i]
5     do exchange A[i],A[PARENT(i)]
6         i ← PARENT(i)

MAX-HEAP-INSERT (A, key)
1 heap-size[A] ← heap-size[A] + 1
2 A[heap-size[A]] ← -∞
3 HEAP-INCREASE-KEY(A, heap-size[A], key)





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章