1. 程式人生 > 其它 >二、排序方法之堆排序(資料結構)

二、排序方法之堆排序(資料結構)

技術標籤:資料結構排序演算法堆排序

堆排序的理解

作為選擇排序的升級版,堆排序的優化體現在何處呢?具體需要如何實現呢?

一、寫在前面

優先佇列(堆)

它可以用於花費 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 ) */
}

二話不說,先上栗子:

待排序192103114125136147158
建堆1325674121013914111518

中間步驟,進行倒數第二層的下沉操作;再進行倒數第三層的下沉操作;最後進行第一層的下沉。

三、堆排序的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);
    }
}

在這裡插入圖片描述

在這裡插入圖片描述