資料結構學習筆記-堆排序

zeta186012發表於2024-06-09

堆排序演算法的設計與分析

問題描述:設計並分析堆排序

【前置知識:什麼是堆?】

堆(Heap)是一種特殊的樹形資料結構,它滿足以下兩個條件之一:

  1. 最大堆(Max Heap)
    • 每個節點的值都大於或等於其子節點的值。
    • 換句話說,根節點的值是整個堆中最大的。
  2. 最小堆(Min Heap)
    • 每個節點的值都小於或等於其子節點的值。
    • 換句話說,根節點的值是整個堆中最小的。

堆的基本特點

  • 完全二叉樹:堆通常被實現為完全二叉樹,也就是說,除了最後一層,所有層都是滿的,而且最後一層的節點都是從左到右排列的。
  • 堆的性質:對於最大堆,任意節點的值都大於或等於其子節點的值;對於最小堆,任意節點的值都小於或等於其子節點的值。

堆的操作

堆支援以下基本操作:

  1. 插入(Insert)
    • 在堆中插入一個新元素,首先將新元素放在堆的末尾,然後透過上浮操作使其恢復堆的性質。
  2. 刪除(Delete)
    • 刪除堆頂元素(最大堆的最大值或最小堆的最小值),用堆的最後一個元素替換堆頂元素,然後透過下沉操作使其恢復堆的性質。
  3. 堆化(Heapify)
    • 將一個無序陣列調整為堆結構,可以透過自下而上的方法調整樹的每個節點來實現。

堆的應用

堆有很多應用場景,包括但不限於:

  • 優先佇列(Priority Queue):堆可以高效地實現優先佇列,支援快速插入和刪除操作。
  • 堆排序(Heap Sort):一種利用堆的性質進行排序的演算法。
  • 求中位數:使用兩個堆(一個最大堆和一個最小堆)可以高效地求動態資料流的中位數。

【演算法設計思想】

構建最大堆

  • 從無序陣列構建一個最大堆。最大堆是指每個節點的值都大於或等於其子節點的值,這樣堆頂元素(即陣列的第一個元素)就是整個陣列的最大值。

交換堆頂和堆的最後一個元素

  • 將堆頂元素(即最大值)和堆的最後一個元素交換,這樣最大值就被放到陣列的末尾,接下來只需對前面部分進行堆調整即可。

堆調整

  • 調整交換後的堆,使其再次成為最大堆。這個過程稱為堆調整(Heapify),確保交換後的堆依然滿足堆的性質。

重複步驟2和步驟3

  • 不斷將堆頂元素和堆的最後一個元素交換,然後對前面的部分進行堆調整,直到所有元素都被排序。

【演算法描述】

// 堆調整函式,維護最大堆性質
void heapify(int arr[], int n, int i) {
    int largest = i;     // 初始化為根節點
    int left = 2 * i + 1; // 左子節點
    int right = 2 * i + 2; // 右子節點

    // 如果左子節點大於根節點
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子節點大於當前最大的節點
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根節點
    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        // 遞迴呼叫heapify,維護堆性質
        heapify(arr, n, largest);
    }
}

// 主函式,進行堆排序
void heapSort(int arr[], int n) {
    // 構建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 一個個取出元素進行排序
    for (int i = n - 1; i > 0; i--) {
        // 將當前根節點移到末尾
        swap(&arr[0], &arr[i]);
        // 調整堆
        heapify(arr, i, 0);
    }
}

【完整的測試程式】

#include <stdio.h>

// 交換兩個元素的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 堆調整函式,維護最大堆性質
void heapify(int arr[], int n, int i) {
    int largest = i;     // 初始化為根節點
    int left = 2 * i + 1; // 左子節點
    int right = 2 * i + 2; // 右子節點

    // 如果左子節點大於根節點
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子節點大於當前最大的節點
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根節點
    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        // 遞迴呼叫heapify,維護堆性質
        heapify(arr, n, largest);
    }
}

// 主函式,進行堆排序
void heapSort(int arr[], int n) {
    // 構建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 一個個取出元素進行排序
    for (int i = n - 1; i > 0; i--) {
        // 將當前根節點移到末尾
        swap(&arr[0], &arr[i]);
        // 調整堆
        heapify(arr, i, 0);
    }
}

// 列印陣列
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");
}

// 測試堆排序
int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("排序前的陣列:\n");
    printArray(arr, n);

    heapSort(arr, n);

    printf("排序後的陣列:\n");
    printArray(arr, n);

    return 0;
}

相關文章