排序演算法之希爾排序和堆排序
阿新 • • 發佈:2019-02-09
希爾排序簡述:
希爾排序可以看作是直接插入排序的一種優化,即把一個數插入一個有序表,不過希爾排序多了一個預設的增量,把一個序列分成若干個增量大小的增量序列,然後在子序列直接進行直接插入排序,每次都使較小的數排到靠前的子序列裡面,增量不斷減小,子序列也不斷縮小,最終使整個表有序。
排序前的準備:
準備了一個預設的順序表
#define MAXSIZE 10 //順序表結構 template <class T> struct SqList { T s[MAXSIZE + 1] = { NULL, 98, 24, 55, 81, 32, 77, 48, 60, 14, 8 }; //陣列s,s[0]用作哨兵 int length = MAXSIZE; //s長度 void PrintArray(); }; typedef SqList<int> SList; //交換l中的陣列中下標為i和j的值 void Swap(SList *L, int i, int j) { int temp = L->s[i]; L->s[i] = L->s[j]; L->s[j] = temp; }
希爾排序實現:
這裡的length為10,增量選擇為length/3 +1=4。
增量的選擇可以是任意小於length的一個數,但最後要等於1,即能進行最後一次直接插入排序即可。
//希爾排序 //設定一個增量,來進行直接插入排序。 void ShellSort(SList *L) { int i, j; int increment = L->length; //增量 do { increment = increment / 3 + 1; //增量 for (i = increment+1; i <= L->length; i++) //遍歷從第一個增量序列的後一個元素開始 { if (L->s[i] < L->s[i-increment]) //增量序列後一個元素小於增量序列的第一個元素 { L->s[0] = L->s[i]; //把s[0]哨兵賦值為s[i] for (j = i-increment; j>0 && L->s[0]<L->s[j] ;j-= increment) {//只迴圈一次 L->s[j + increment] = L->s[j]; //將增量序列後一個元素賦值為增量序列的第一個元素 } L->s[j+increment] = L->s[0]; //最後改變增量序列的第一個元素為哨兵值 } } } while (increment > 1); //當增量小於等於1時終止迴圈 }
希爾排序時間複雜度:
希爾排序的時間複雜度和增量的選取有關係,並不是完全相同的。
最優情況下的時間複雜度為O(n^1.3);
最壞情況下的時間複雜度為O(n^2);
堆排序簡述:
堆排序利用了完全二叉樹的性質來進行,堆排序把一個順序表先構造成一個大頂堆,大頂堆即所有父節點比子節點大的一個完全二叉樹,構造成大頂堆後,把根節點(即當前最大值)和最後的元素交換,並繼續把長度減一的剩餘順序表構造成大頂堆,如此迴圈。
堆排序實現:
這裡碰到了一個問題是如何將順序表構造成大頂堆?
程式碼如下:
堆排序如下://構造成大頂堆 void HeapAdjust(SList *L,int i,int length) { int temp, j; temp = L->s[i]; //臨時變數賦值為當前父節點,用於後續的比較 for (j = 2 * i; j <= length; j *= 2) //j=2*i指向左孩子節點 { if (L->s[j]<L->s[j+1] && j<length) //如果左孩子比右孩子小,則j指向右孩子.j<length說明j不是最後節點 { ++j; } if (temp>L->s[j]) //如果一開始的父節點比兩個子節點都大,break { break; } L->s[i] = L->s[j]; //父節點比子節點小,把父節點賦值為兩個孩子中較大的一個 i = j; //父節點指向子節點中較大的一個繼續迴圈 } L->s[i] = temp; //把子節點較大的一個賦值為父節點 }
//堆排序
//將無序數列構造成大頂堆,把根節點和層序遍歷最後節點互換,然後把剩下元素繼續構造大頂堆
void HeapSort(SList *L)
{
int i;
for (i = L->length / 2; i > 0; i--) //i=Length/2 因為完全二叉樹的父節點數量為葉子節點的一半
{
HeapAdjust(L, i, L->length); //將整個序列構造成大頂堆
}
for (i = L->length; i > 1; i--)
{
Swap(L, 1, i); //把根節點和最後一位互換
HeapAdjust(L, 1, i - 1); //繼續構造大頂堆
}
}
程式首先從完全二叉樹的父節點開始遍歷到根節點(即(length/2)個父節點),將這些父節點下面的小子樹分別構造成大頂堆,最終得到一個完全的大頂堆;
然後從尾到頭開始遍歷,把最大的數和最後節點交換,並繼續遍歷除最大節點意外剩餘節點,最終即得到一個從小到大的有序表。
堆排序時間複雜度:
構建一個大頂堆從最下層最右邊的父節點開始遍歷,進行交換,構造堆需要的時間複雜度為O(n);
而重建堆需要的時間複雜度為O(nlogn),所以整體的堆排序時間複雜度為O(nlogn)。
堆排序不適合元素數量較少的情況。