1. 程式人生 > >演算法導論複習(3) 堆排序

演算法導論複習(3) 堆排序

   堆排序與歸併排序具有相同的時間複雜度O(nlgn),但是在講堆排序之前,先要搞清楚堆排序使用的“二叉堆”

二叉堆是一個數組,可以被看成近似的完全二叉樹
特點:
1.樹上每一節點對應一個元素,除最底層外,樹是完全充滿的,而且從左到右填充。
2.
a(大頂堆):根節點的值是大於等於任何子節點的值
b(小頂堆):根節點的值是小於等於任何子節點的值
3.每個節點的左子樹和右子樹均為一個二叉堆
通過以上特點,將一個數組的所有元素依次放入二叉堆中,設數的根節點為a[i],給定一個節點下標為i,則可以得到節點的父節點,左孩子和右孩子的下標:
父節點:i/2
左孩子:2*i
右孩子:2*i+1
介紹完基本知識,書本上介紹了最大堆排序的步驟及時間複雜度:

MAX-HEAPIFY(對於單個二叉堆的排序演算法) 時間複雜度O(lgn)
BUILD-MAX-HEAP(建二叉堆演算法) 時間複雜度O(n)
HEAPSORT(對陣列排序) 時間複雜度O(nlgn)

一、MAX-HEAPIFY
這個步驟的目的是維護最大堆性質,輸入為陣列a和下標i,在呼叫MAX-HEAPIFY時,假定左子樹和右子樹均為最大堆,但a[i]可能小於孩子,所以通過讓a[i]逐級下降,使得下標i為根節點的子樹滿足最大堆性質。

程式碼展示:

int maxheapify(int *a,int i,int size)
{
    int l=2*i;           //左子樹
int r=2*i+1; //右子樹 int largest; if(i<=size/2) { if(l<=size&&a[l]>a[i]) //左孩子與根節點 { largest=l; } else { largest=i; } if(r<=size&&a[r]>a[largest]) //右孩子與根節點 { largest=r; } if(largest!=i) { swap(a[i],a[largest]); maxheapify(a,largest,size
); } } }

分析時間代價:
1。調整a[i]和左孩子、右孩子關係θ(1)
2。以i的一個孩子為根節點的子樹執行MAX-HEAPIFY時間
遞迴式:T(n)=T(2n/3)+θ(1)
遞迴式的解為O(lgn)(主定理方法2),所以樹高為n的結點,MAX-HEAPIFY時間複雜度為O(n)

二、建堆
根節點下標為a.length/2,葉節點下標為(n/2+1)、(n/2+2)…(n),所以對a.length/2到1做MAX-HEAPIFY操作
int buildheap(int *a,int size)
{
for(int i=(size/2);i>=1;i–)
{
maxheapify(a,i,size);
}
}
建堆的時間複雜度在88頁有詳細的介紹,這裡不詳細介紹了,主要的就是建堆的時間複雜度是O(n).

三、堆排序
對大頂堆,因為根節點的值是陣列中也是二叉堆中最大的,所以每次把根節點取走,然後剩下的排出最大根節點再取走,依次就可以完成陣列從大到小排序,反之從小到大排序是在小頂堆中每次取根節點所得。
程式碼:

int heapsort(int *a,int size)
{
    buildheap(a,size);
    for(int i=size;i>=1;i--)
    {
        swap(a[1],a[i]);
        size=size-1;
        maxheapify(a,1,size);
    }
}

因為是建堆的時間複雜度是O(n),要呼叫MAX-HEAPIFY(n-1)次,每次時間O(lgn),所以堆排序的時間複雜度是O(nlgn)