二、排序方法之堆排序(資料結構)
阿新 • • 發佈:2020-12-23
堆排序的理解
作為選擇排序的升級版,堆排序的優化體現在何處呢?具體需要如何實現呢?
一、寫在前面
優先佇列(堆)
它可以用於花費 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N))時間的排序,基於該想法的排序叫做堆排序(heap_sort)並給出我們至今所見到的最佳的大O執行時間。雖然在大部分實踐中,要慢於使用Sedgewick增量序列的希爾排序(詳見希爾排序)
二、堆排序的思想
簡易想法
直接使用建立二叉堆的方法,然後執行N次DeleteMin操作。按照順序,最小的元素先離開堆。通過將這些元素記錄到一個數組中,再將陣列拷貝回來,我們就得到了N個元素的排序。
虛擬碼描述
Algorithm 1:
{
BuildHeap( H ); /*建堆: O( N ) */
for ( i=0; i<N; i++ )
TmpH[ i ] = DeleteMin( H ); /*執行N次DeleteMin,每次耗時: O( log N ) */
for ( i=0; i<N; i++ ) /*拷貝陣列*/
H[ i ] = TmpH[ i ]; /* O( 1 ) */
}
二話不說,先上栗子:
待排序 | 1 | 9 | 2 | 10 | 3 | 11 | 4 | 12 | 5 | 13 | 6 | 14 | 7 | 15 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
建堆 | 1 | 3 | 2 | 5 | 6 | 7 | 4 | 12 | 10 | 13 | 9 | 14 | 11 | 15 | 18 |
中間步驟,進行倒數第二層的下沉操作;再進行倒數第三層的下沉操作;最後進行第一層的下沉。
三、堆排序的C語言實現
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define SIZE 20
//列印輸出陣列
void prt(int a[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%3d ", a[i]);
if (i % 21 == 20)
{
printf("\n");
}
}
printf("\n");
}
// 交換陣列元素位置
void Swap(int *num_a, int *num_b)
{
int temp = *num_b;
*num_b = *num_a;
*num_a = temp;
}
void HeapSort(int array[], int length);
int main()
{
int a[SIZE];
//生成隨機陣列
srand(2);
for (int i = 0; i < SIZE; i++)
{
a[i] = rand() % SIZE;
}
prt(a, SIZE);
printf("-------------------------------------------------\n");
//堆排序
HeapSort(a, SIZE);
prt(a, SIZE);
//判斷結果正確性
int suc = 1;
for (int i = 0; i < SIZE - 1; i++)
{
if (a[i] > a[i + 1])
{
suc = 0;
break;
}
}
if (suc)
{
printf("ok\n");
}
else
{
printf("fail\n");
}
}
// array是待調整的最小堆,i是待調整的陣列元素的位置,nlength是陣列的長度
void HeapAdjust(int array[], int i, int nLength)
{
int nChild; //表示孩子中間小的那個
//從i開始判斷,往下沉
int temp = array[i];
for (; 2 * i + 1 < nLength; i = nChild)
{
nChild = 2 * i + 1; //先判斷左,右孩子的大小關係
if (nChild != nLength - 1 && array[nChild] > array[nChild + 1])
{
nChild++; //右孩子小於左孩子,則選擇右孩子
}
if (array[nChild] < temp)
{
array[i] = array[nChild]; //最大的孩子比該節點小,那麼該節點下沉
}
else
{
break; //結束迴圈:如果下沉到底(下面的元素更大)
}
}
array[i] = temp;
}
// 堆排序演算法
void HeapSort(int array[], int length)
{
int *ret = (int *)malloc(sizeof(int) * length);
// 調整序列的前半部分元素,(即每個有孩子的節點)調整完之後是一個小頂堆,第一個元素是序列的最小的元素
for (int i = length / 2 - 1; i >= 0; --i)
{
HeapAdjust(array, i, length);
}
// 不斷deleteMin,直到刪除最後一個
for (int i = length - 1; i > 0; --i)
{
//拷貝當前元素
ret[length - 1 - i] = array[0];
// DeleteMin: 把第一個元素和當前的最後一個元素交換,
// 保證當前的最後一個位置的元素都是在現在的這個序列之中最小的
Swap(&array[0], &array[i]);
// 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最小值
HeapAdjust(array, 0, i);
}
ret[length - 1] = array[0];
//將ret資料拷貝回array,排序完成
for (int i = 0; i < length; i++)
{
array[i] = ret[i];
}
}
變更實驗資料可以更改隨機數種子,srand(num).
四、實驗結果
堆排序
五、提高與改進
注意到上述程式的空間複雜度是O(N),也就是要多開闢同樣大小的空間,這其實是沒有必要的,我們注意到如果使用最大堆,每次交換元素到最後面的就是最大的,這其實也符合我們插入排序的思想,找到最大的插到最後面。
虛擬碼
void Heapsort( ElementType A[ ], int N )
{ int i;
for ( i = N / 2; i >= 0; i - - ) /* BuildHeap */
PercDown( A, i, N );
for ( i = N - 1; i > 0; i - - ) {
Swap( &A[ 0 ], &A[ i ] ); /* DeleteMax */
PercDown( A, 0, i );
}
}
程式實現:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define SIZE 20
//列印輸出陣列
void prt(int a[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%3d ", a[i]);
if (i % 21 == 20)
{
printf("\n");
}
}
printf("\n");
}
// 交換陣列元素位置
void Swap( int *num_a, int *num_b )
{
int temp = *num_b;
*num_b = *num_a;
*num_a = temp;
}
void HeapSort(int array[], int length);
int main(){
int a[SIZE];
//生成隨機陣列
srand(2);
for(int i=0;i<SIZE;i++){
a[i] = rand() % SIZE;
}
prt(a,SIZE);
printf("-------------------------------------------------\n");
//希爾排序
HeapSort(a,SIZE);
prt(a,SIZE);
//判斷結果正確性
int suc =1;
for(int i=0;i<SIZE-1;i++){
if(a[i] > a[i+1]){
suc =0;break;
}
}
if(suc ){
printf("ok\n");
}else{
printf("fail\n");
}
}
// array是待調整的最大堆,i是待調整的陣列元素的位置,nlength是陣列的長度
void HeapAdjust(int array[], int i, int nLength)
{
int nChild; //表示孩子中間大的那個
//從i開始判斷,往下沉
int temp = array[i];
for(;2 * i +1 < nLength; i = nChild){
nChild = 2*i+1; //先判斷左,右孩子的大小關係
if(nChild != nLength -1 && array[nChild] < array[nChild+1]){
nChild ++; //右孩子大於左孩子,則選擇右孩子
}
if(array[nChild] > temp){
array[i] = array[nChild]; //最大的孩子比該節點大,那麼該節點下沉
}else{
break; //結束迴圈:如果下沉到底(下面的元素更小)
}
}
array[i] = temp;
}
// 堆排序演算法
void HeapSort(int array[], int length)
{
// 調整序列的前半部分元素,(即每個有孩子的節點)調整完之後是一個大頂堆,第一個元素是序列的最大的元素
for (int i = length / 2 - 1; i >= 0; --i)
{
HeapAdjust(array, i, length);
}
// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for (int i = length - 1; i > 0; --i)
{
// 把第一個元素和當前的最後一個元素交換,
// 保證當前的最後一個位置的元素都是在現在的這個序列之中最大的
Swap(&array[0], &array[i]);
// 不斷縮小調整heap的範圍,每一次調整完畢保證第一個元素是當前序列的最大值
HeapAdjust(array, 0, i);
}
}