排序算法-8-堆排序

堆排序

堆排序是利用堆(Heap)來進行排序的一種方法,在堆排序中,我們需要引入一種叫做“堆”的數據結構。

堆一般可以看做是一棵完全二叉樹,且堆中某個節點的值總是不大於或不小於其父節點的值,前者我們稱之爲最大堆,後者稱之爲最小堆。

原理

堆排序的思想是先用所有元素建立一個最大堆(當然,這裏用最小堆也是可以的,方法類似最大堆,不失一般性,就只講最大堆了),然後每次取出最大的那個元素,並把剩下的元素再次調整爲一個最大堆,如此循環。步驟如下:

  1. 用所有元素建立一個最大堆。
  2. 取出最大的元素,將其與堆中的最後一個元素交換。
  3. 因爲已經取出了一個元素,將堆的大小減一。
  4. 調整剩下的堆中元素重新組成一個最大堆。
  5. 重複2-4步,直到堆中只剩最後一個元素爲止。

建堆和調整堆爲最大堆都需要用到堆調整的功能,其前提是調整前只有頂點元素不滿足最大堆的條件。此功能步驟如下:

  1. 記錄頂點元素的值。
  2. 找到頂點、其左孩子、其右孩子三者中的最大一個,若最大者爲頂點,結束;否則交換最大者與頂點。
  3. 對以最大者爲根的子堆重複1,2步,直到結束。
    注意這裏第3步的實現可以用遞歸,也可以用循環。

實現

按照以上原理我們來用代碼實現。

下面就是用C語言實現的代碼。分成兩個函數來實現。

  • 要排序的數組a有n個元素。
  • heap_sort 先建最大堆,然後每次取出最大的那個元素a[0]放入已排序區,再調用 heap_adjust 調整剩下的堆爲最大堆。
  • heap_adjust 的輸入中p、r分別指向堆的第一個元素和最後一個元素。每次找到父親節點、左孩子、右孩子中最大的那個,將其調整爲父親節點,然後如有需要,繼續調整子堆。這裏沒有用遞歸,用了循環。
/* p、r分別指向堆的第一個元素和最後一個元素 */
void heap_adjust(int a[], int p, int r)
{
	int tmp = a[p];                   //p指向要調整加入的堆頂點,tmp是其值

	for (int i=2*p+1; i<=r; i=2*p+1) {//a[i]爲a[p]的左孩子,a[i+1]爲a[p]的右孩子
		if (i<r && a[i]<a[i+1]) i++;  //找出兩個孩子中大的一個,用i指向他
		if (tmp > a[i]) break;        //若父親比最小的孩子大,則已調整好
		a[p] = a[i];                  //否則將最大的孩子調整爲父親,
		p = i;                        //並將p指向要調整的子堆頂點
	}
	a[p] = tmp;                       //將要加入的元素放入調整好的位置
}

void heap_sort(int a[], int n)
{
	int i;

	/* 建最大堆 */
	for (i=n/2-1; i>=0; i--)
		heap_adjust(a, i, n-1);

	/* 每次取出當前最大元素a[0],然後調整堆爲最大堆 */
	for (i=n-1; i>0; i--) {
		swap(&a[0], &a[i]);     // 將a[0]放到已排序區域
		heap_adjust(a, 0, i-1); // 調整剩下的堆爲最大堆
	}
}

void swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

爲了驗證此函數的效果,加上了如下輔助代碼,對3個數組進行排序,運行結果在最後,可見排序成功。

#include <stdio.h>
#include <stdlib.h>

#define SIZE_ARRAY_1 5
#define SIZE_ARRAY_2 6
#define SIZE_ARRAY_3 20

void heap_sort(int a[], int n);
void show_array(int a[], int n);

void main()
{
	int array1[SIZE_ARRAY_1]={1,4,2,-9,0};
	int array2[SIZE_ARRAY_2]={10,5,2,1,9,2};
	int array3[SIZE_ARRAY_3];

	for(int i=0; i<SIZE_ARRAY_3; i++) {
		array3[i] = (int)((40.0*rand())/(RAND_MAX+1.0)-20);
	}

	printf("Before sort, ");
	show_array(array1, SIZE_ARRAY_1);
	heap_sort(array1, SIZE_ARRAY_1);
	printf("After sort, ");
	show_array(array1, SIZE_ARRAY_1);

	printf("Before sort, ");
	show_array(array2, SIZE_ARRAY_2);
	heap_sort(array2, SIZE_ARRAY_2);
	printf("After sort, ");
	show_array(array2, SIZE_ARRAY_2);

	printf("Before sort, ");
	show_array(array3, SIZE_ARRAY_3);
	heap_sort(array3, SIZE_ARRAY_3);
	printf("After sort, ");
	show_array(array3, SIZE_ARRAY_3);
}

void show_array(int a[], int n)
{
	if(n>0)
		printf("This array has %d items: ", n);
	else
		printf("Error: array size should bigger than zero.\n");

	for(int i=0; i<n; i++) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

運行結果:

Before sort, This array has 5 items: 1 4 2 -9 0
After sort, This array has 5 items: -9 0 1 2 4
Before sort, This array has 6 items: 10 5 2 1 9 2
After sort, This array has 6 items: 1 2 2 5 9 10
Before sort, This array has 20 items: 13 -4 11 11 16 -12 -6 10 -8 2 0 5 -5 0 18 16 5 8 -14 4
After sort, This array has 20 items: -14 -12 -8 -6 -5 -4 0 0 2 4 5 5 8 10 11 11 13 16 16 18

分析

時間複雜度

從代碼可見,heap_adjust 的過程是把二叉樹的頂點元素往子樹移動,最多移動到葉節點,即最多有 logn\log n 次,所以 heap_adjust 的時間複雜度爲 O(logn)O(\log n)

而 heap_sort 中的 for 循環會遍歷一次整個數組,量級爲 nn,for 循環中調用 heap_adjust,所以整個堆排序的時間複雜度爲 O(nlogn)O(n\log n)

空間複雜度

因爲堆排序可以原址進行,所以其空間複雜度爲 O(1)O(1)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章