堆排序
堆排序是利用堆(Heap)來進行排序的一種方法,在堆排序中,我們需要引入一種叫做“堆”的數據結構。
堆一般可以看做是一棵完全二叉樹,且堆中某個節點的值總是不大於或不小於其父節點的值,前者我們稱之爲最大堆,後者稱之爲最小堆。
原理
堆排序的思想是先用所有元素建立一個最大堆(當然,這裏用最小堆也是可以的,方法類似最大堆,不失一般性,就只講最大堆了),然後每次取出最大的那個元素,並把剩下的元素再次調整爲一個最大堆,如此循環。步驟如下:
- 用所有元素建立一個最大堆。
- 取出最大的元素,將其與堆中的最後一個元素交換。
- 因爲已經取出了一個元素,將堆的大小減一。
- 調整剩下的堆中元素重新組成一個最大堆。
- 重複2-4步,直到堆中只剩最後一個元素爲止。
建堆和調整堆爲最大堆都需要用到堆調整的功能,其前提是調整前只有頂點元素不滿足最大堆的條件。此功能步驟如下:
- 記錄頂點元素的值。
- 找到頂點、其左孩子、其右孩子三者中的最大一個,若最大者爲頂點,結束;否則交換最大者與頂點。
- 對以最大者爲根的子堆重複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 的過程是把二叉樹的頂點元素往子樹移動,最多移動到葉節點,即最多有 次,所以 heap_adjust 的時間複雜度爲 。
而 heap_sort 中的 for 循環會遍歷一次整個數組,量級爲 ,for 循環中調用 heap_adjust,所以整個堆排序的時間複雜度爲 。
空間複雜度
因爲堆排序可以原址進行,所以其空間複雜度爲 。