1. 程式人生 > >C++理解並實現堆排序

C++理解並實現堆排序

摘要

       作為選擇排序的改進版,堆排序可以把每一趟元素的比較結果儲存下來,以便我們在選擇最小/大元素時對已經比較過的元素做出相應的調整。

       堆排序是一種樹形選擇排序,在排序過程中可以把元素看成是一顆完全二叉樹,每個節點都大(小)於它的兩個子節點,當每個節點都大於等於它的兩個子節點時,就稱為大頂堆,也叫堆有序; 當每個節點都小於等於它的兩個子節點時,就稱為小頂堆。

                             

             (大頂堆(有序堆))                                                                                     (小頂堆)

演算法思想(以大頂堆為例)

1.將長度為n的待排序的陣列進行堆有序化構造成一個大頂堆

2.將根節點與尾節點交換並輸出此時的尾節點

3.將剩餘的n -1個節點重新進行堆有序化

4.重複步驟2,步驟3直至構造成一個有序序列

假設待排序陣列為[20,50,10,30,70,20,80]

構造堆

在構造有序堆時,我們開始只需要掃描一半的元素(n/2-1 ~ 0)即可,為什麼?

因為(n/2-1)~0的節點才有子節點,如圖1,n=8,(n/2-1) = 3  即3 2 1 0這個四個節點才有子節點

                  (圖1:初始狀態)

所以程式碼4~6行for迴圈的作用就是將3 2 1 0這四個節點從下到上,從右到左的與它自己的子節點比較並調整最終形成大頂堆,過程如下:

第一次for迴圈將節點3和它的子節點7 8的元素進行比較,最大者作為父節點(即元素60作為父節點)

【紅色表示交換後的狀態】

 

第二次for迴圈將節點2和它的子節點5 6的元素進行比較,最大者為父節點(元素80作為父節點)

 

第三次for迴圈將節點1和它的子節點3 4的元素進行比較,最大者為父節點(元素70作為父節點)

第四次for迴圈將節點0和它的子節點1 2的元素進行比較,最大者為父節點(元素80作為父節點)

(注意這裡,元素20和元素80交換後,20所在的節點還有子節點,所以還要再和它的子節點5 6的元素進行比較,這就是28行程式碼 i = j 

的原因)

至此有序堆已經構造好了!如下圖:

 

調整堆

下面進行while迴圈

(1)堆頂元素80和尾40交換後-->調整堆

(2)堆頂元素70和尾30交換後-->調整堆

(3)堆頂元素60尾元素20交換後-->調整堆

(4)其他依次類推,最終已排好序的元素如下:

 

(5) 我們可以用一個動態圖來理解堆排序

(6) 排序演算法總結

堆排序是不穩定的排序演算法,不穩定發生在堆頂元素與A[i]交換的時刻。

比如序列:{ 9, 5, 7, 5 },堆頂元素是9,堆排序下一步將9和第二個5進行交換,得到序列 { 5, 5, 7, 9 },再進行堆調整得到{ 7, 5, 5, 9 },重複之前的操作最後得到{ 5, 5, 7, 9 }從而改變了兩個5的相對次序。

C++(STL程式碼)

#include<iostream>
#include<vector>
using namespace std;
/***************************************************************
				本程式實現堆排序,堆排序是一種時間複雜度為
				O(nlogn)的不穩定排序演算法,最好和最壞情況都
				是O(nlogn),常用排序演算法連結:
				https://www.cnblogs.com/eniac12/p/5329396.html
				anthor:李金澤,BioInformation Lab,HIT 2018.11.01
*****************************************************************/
template<typename T>
void Swap(vector<T>& a, int i, int j) {
	T temp;
	temp = a[i];
	a[i] = a[j];
	a[j] = temp;
}
template<typename T>
void HeapSort(vector<T>& a) {
	int len = a.size() - 1;
	for (int i = len / 2 - 1; i >= 0; i--) {//構造一個大頂堆,這時的a[0]為最大值
		AdjustHeap(a, i, len);
	}
	while (len >= 0) {
		Swap(a, 0, len--);
		AdjustHeap(a, 0, len);//調整堆,使其滿足堆的定義(父節點比它的倆子節點都要大)
	}
}
template<typename T>
void AdjustHeap(vector<T>& a, int i, int len)
{
	int left, right;//存放左右節點的下標
	int MaxPoint;//存放較大子節點的下標
	while ((left = 2 * i + 1) < len) {
		right = left + 1;
		MaxPoint = left;
		if (left < len && a[right] > a[left]) {//當left==len時,i節點沒有右子節點,MaxPoint指標指向左節點
			MaxPoint++;
		}
		if (a[i] < a[MaxPoint]) {
			Swap(a, i, MaxPoint);
		}
		else { break; }
		i = MaxPoint;//將父節點設定為子節點,開始向子樹調整堆結構
	}
}
int main()
{
	vector<int> a = { 20,50,20,40,70,10,80,30,60 };
	cout << "排序之前:" << endl;
	for (int i = 0; i < a.size(); i++) {
		cout << a[i]<<" ";
	}
	HeapSort<int>(a);
	cout << "排序之後:" << endl;
	for (int i = 0; i < a.size(); i++) {
		cout << a[i]<<" ";
	}
	getchar();
	return 0;
}

Java程式碼:

public class HeapSort {
    private static void heapSort(int[] arr) {
        int len = arr.length -1;
        for(int i = len/2 - 1; i >=0; i --){ //堆構造
            heapAdjust(arr,i,len);
        }
        while (len >=0){
            swap(arr,0,len--);    //將堆頂元素與尾節點交換後,長度減1,尾元素最大
            heapAdjust(arr,0,len);    //再次對堆進行調整
        }
    }
 
public static  void heapAdjust(int[] arr,int i,int len){
    int left,right,j ;
    while((left = 2*i+1) <= len){    //判斷當前父節點有無左節點(即有無孩子節點,left為左節點)
        right = left + 1;  //右節點
        j = left;   //j"指標指向左節點"
        if(j < len && arr[left] < arr[right])    //右節點大於左節點
            j ++;     //當前把"指標"指向右節點
        if(arr[i] < arr[j])    //將父節點與孩子節點交換(如果上面if為真,則arr[j]為右節點,如果為假arr[j]則為左節點)
            swap(arr,i,j);
        else         //說明比孩子節點都大,直接跳出迴圈語句
            break;
        i = j;
    }
}
    public static  void swap(int[] arr,int i,int len){
             int temp = arr[i];
              arr[i] = arr[len];
             arr[len] = temp;
    }
    public static void main(String[] args) {
        int array[] = {20,50,20,40,70,10,80,30,60};
        System.out.println("排序之前:");
        for(int element : array){
            System.out.print(element+" ");
        }
        heapSort(array);
        System.out.println("\n排序之後:");
        for(int element : array){
            System.out.print(element+" ");
        }
    }
}