堆排序、堆排序優化、索引堆排序

堆排序、堆排序優化、索引堆排序git

注: 堆排序、索引堆排序 都是不穩定的排序。
注:索引最大堆排序有誤!!!有沒有大神能夠指點一二???github

一、堆:
全部元素 都從索引0開始web

父親結點索引:i;
左孩子結點索引: 2i+1;
右孩子結點索引: 2
i+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

測試結果以下圖所示:
這裏寫圖片描述

從測試結果看, 前倆個版本是好的, 可是,索引最大堆排序有問題!!目前找不到問題所在,還望路過的大神指點一二!!