堆排序、堆排序優化、索引堆排序git
注: 堆排序、索引堆排序 都是不穩定的排序。
注:索引最大堆排序有誤!!!有沒有大神能夠指點一二???github
一、堆:
全部元素 都從索引0開始web
父親結點索引:i;
左孩子結點索引: 2i+1;
右孩子結點索引: 2i+2;
左後一個非葉子結點索引:(n-1)/2; 用於構建堆,從最後一個非葉子結點索引開始調整堆,直至到達索引爲0的首個父親結點數組
二、堆排序(升序爲例):
共兩步:
step一、構建堆
step二、原地堆排序數據結構
step一、構建堆
從最後一個非葉子結點索引開始調整堆,直至到達索引爲0的首個父親結點。
step二、原地堆排序:
每次循環,使用O(1)的時間索引到當前循環的最大值a[0],
將該最大值交換到數組末尾,
數組元素減1,
對新的堆進行調整,爲下一輪循環作準備。svg
三、調整堆函數shiftDown()思路:
從當前父親結點k開始,
每次比較 當前父親結點值與該父親結點對應的左右孩子節點中的最大值,
若是 「父親結點值」>「最大孩子節點值」,就表明堆調整好了,提早結束循環。
若是 「父親結點值」<「最大孩子節點值」,那麼,當前"父親結點值"更換爲「最大孩子節點值"。更新父親結點值,繼續向下調整。函數
四、區別
4.一、普通堆排序的 shiftDown中使用「移動賦值」操做 與 「交換數據元素」 操做 區別:
使用「移動賦值」操做 比 使用"交換數據元素" 操做,的時間損耗少了2/3.
eg:shiftDown中總共進行m次循環,
—使用「移動賦值」操做:全部操做數爲:m次移動+1次賦值+1個額外空間的申請,共計 m+1次 賦值操做 + 1個額外空間的申請
—使用「交換數據」操做:全部操做數爲:3m次賦值+1個額外空間的申請, 共計 3m 次賦值操做 + 1個額外空間的申請測試
4.二、索引堆排序 與 普通堆排序 區別:
(1)定義
索引堆:數據域 與 索引域 是分開存儲的。
排序過程當中:數據元素的相對位置保持不變,這樣能夠使得 堆排序 優化爲穩定的排序;改變的是索引數組的相對位置。
最後造成的索引數組,就是所謂的索引堆。優化
索引堆排序:
不改變原來數據域元素的位置,只是新開闢了一個索引域來表明原來數據域的相應數據進行排序,其本質仍是一個堆排序。重點內容code
(2)排序
比較值大小時,用的是 數據域 data數組中的元素
移動、賦值、交換時,用的是 索引域 index數組中的元素
(3)排序輸出
索引堆排序:輸出時,只需依次取出索引數組中對應索引的對應數據域元素,便可。
普通堆排序:依次輸出,改變後的數組元素。
(4)消耗
索引堆排序 比 普通堆排序 多佔用一個O(n)的int型空間,用於存放代替數據元素進行堆排序的索引數據。
(5)優勢
索引堆排序與普通堆排序,優勢爲:
若是原來數據域中,每個元素的數據結構很複雜,或者數據的size都很大,那麼,使用普通堆排序,在移動、賦值、交換數據域的元素過程花費會很是的巨大。
而使用索引堆排序,則只是花費了一個O(n)的int類型空間,在在移動、賦值、交換操做中,都是一個int型的索引元素在參與運算,花費很是小。
五、核心代碼:
///////////////////////////////////////////////////////// //三個版本的原地堆排序 //version1 最大堆排序 shiftDown()中"交換數據元素"操做 void shiftDown(int a[], int n, int k){//以k爲開始調整的父節點,自上而下調整 while (2 * k + 1 < n){//存在孩子節點時,最少存在一個「左孩子結點」 int j = 2 * k + 1;//j:左右孩子結點最大值的索引 初始化爲 左孩子結點索引 if (j + 1 < n&&a[j + 1] > a[j])j += 1;//若是存在右孩子結點,且右孩子結點值大於左孩子結點值,更新孩子節點最大值索引爲右孩子結點索引。 if (a[k] > a[j])break;//循環提早結束標誌,噹噹前父節點值大於該父節點對應的最大孩子節點值時,堆調整好啦,退出循環 swap(a[k], a[j]);//交換 不然的話,交換父節點與最大值孩子節點 k = j;//更新父親結點爲當前最大值孩子節點,繼續向下調整堆 } } void maxHeapSort(int a[], int n){ //建堆 for (int i = (n - 1) / 2; i >= 0; --i) shiftDown(a, n, i); //顯示堆 cout << "建堆" << endl; printArr(a, n); //原地堆排序 for (int i = n - 1; i > 0; --i){//i:每輪循環要處理的堆元素個數 swap(a[0], a[i]);//交換最大值到數組末尾 shiftDown(a, i, 0);//調整去掉最大值後的剩餘堆元素 爲最大堆 } } //version2 最大堆排序優化 shiftDown()中使用「移動賦值」操做取代"交換數據元素" 操做 //思路源於 插入排序 void shiftDown2(int a[], int n, int k){ int tmp = a[k]; while (2 * k + 1 < n){ int j = 2 * k + 1; if (j + 1 < n && a[j + 1] > a[j])j += 1; if (tmp > a[j])break; a[k] = a[j];//移動 k = j;//更新 父親結點的索引,切記!! } a[k] = tmp;//賦值 } //有問題 !!!version3 最大索引堆排序 shiftDown()中使用「移動賦值」操做 void shiftDown3(int a[], int index[], int n, int k){ int tmp_index = index[k];//開始的臨時變量是 索引爲k的data域中的元素,即 第索引號爲k的索引域中的元素 index[k] while (2 * k + 1 < n){ int j = 2 * k + 1; if (j + 1 < n && a[index[j + 1]] > a[index[j]])j += 1;//a[index[j + 1]] > a[index[j]]:比較的是數據域中的元素值 if (a[tmp_index] > a[j])break;//a[tmp_index] > a[j]:比較的是數據域中的元素值 index[k] = index[j];//移動的是 索引域中元素 k = j; } index[k] = tmp_index;//賦值的是 索引域中元素 } void IndexMaxHeapSort(int a[], int index[], int n){ for (int i = (n - 1) / 2; i >= 0; --i)//建堆 shiftDown3(a, index, n, i); cout << "建堆" << endl; printIndexMaxHeap(a, index, n); for (int i = n - 1; i > 0; --i){//原地堆排序 swap(index[0], index[i]); shiftDown3(a, index, i, 0); } } void printIndexMaxHeap(int a[], int index[], int n){ for (int i = 0; i < n; ++i) cout << a[index[i]] << "\t"; cout << endl; } //version3 end
六、完整代碼,請移步個人GitHub
https://github.com/MissStrickland/maxHeapSort_indexMaxHeapSort/blob/master/main_heapSort.cpp
七、測試:
起始數據: 2 9 5 6 4 10 8 3 5 8
//建成堆以下:
10 / \ 9 8 / \ / \ 6 8 5 2 / \ / 3 5 4
測試結果以下圖所示:
從測試結果看, 前倆個版本是好的, 可是,索引最大堆排序有問題!!目前找不到問題所在,還望路過的大神指點一二!!