[算法導論]堆排序

主要測試大堆,測試源碼在(github)


堆可以看成一個近似的完全二叉樹,除了底層外,該樹是完全充滿的,而且是從左到右填充。

完全二叉樹適合用數組來存儲。用數組來存儲完全二叉樹是非常節省存儲空間的。å

最大堆中,是指除了根結點外(根結點沒有 parent),所有結點的 i 都應該滿足:
A[PARENT(i)]A[i] A[PARENT(i)] \ge A[i]
堆數據-數組-二叉樹
堆被看作是一個完全二叉樹,那麼該堆堆高度成正比O(lgn),我們會發現,堆結構上的一些基本操作的運行時間至多與樹的高度成正比,即實際複雜度爲O(lgn)。


堆排序過程:

MAX-HEAPIFY(堆化):將堆的末端子節點作調整,使得子節點永遠小於父節點。
BUILD-MAX-HEAD(建堆):從無序數據數組中構造一個最大堆。
HEAPSORT(排序):對數組進行原址排序,移除位在第一個數據的根節點,並做最大堆調整的遞歸運算。


堆化

堆化讓數值大的結點往上浮,讓小的結點逐漸下沉。
堆化流程.png
堆化僞代碼

void HeapSort::max_heapify(int array[], int size, int i) {
    int largest = i;
    int l = left(i);
    int r = right(i);

    if (l <= size && array[l] > array[i]) {
        largest = l;
    }

    if (r <= size && array[r] > array[largest]) {
        largest = r;
    }

    if (largest != i) {
        swap(&array[largest], &array[i]);
        max_heapify(array, size, largest);
    }
}

建堆

我們可以通過二叉樹結點自底向上的方法,利用堆化過程,把一個大小爲 n = A.length 的數組 A = [1…n] 轉換爲最大堆。BUILD-MAX-HEAP 時間複雜度爲 O(n)
建堆僞代碼

void HeapSort::build_max_heap(int array[], int len) {
    for (int i = (len / 2); i >= 1; i--) {
        max_heapify(array, len, i);
    }
}

排序

BUILD-MAX-HEAP 會將數組A[1…n]建成最大堆。最大堆元素在根結點A[1],去掉 A[1] 後,A[1]的左右孩子結點仍然是最大堆。如果把 A[n] 替換 A[1],破壞了最大堆性質,將重新對 A[1…n-1] 數組進行堆化,使其變成最大堆。如此遞歸重複以上步驟。
排序流程
排序流程

void HeapSort::heap_sort() {
    if (m_data_size <= 1) {
        return;
    }

    // 建堆處理後,父結點 > 子結點
    build_max_heap(m_array, m_data_size);
    // 建堆後,堆頂結點(根結點)是最大數值,把最大值放到數組最後。原數組最後一個結點置換到根結點。
    swap(&m_array[1], &m_array[m_data_size]);

    // 排除數組最後一個元素,再對剩餘堆進行堆化,再把堆化的根結點放到數組最後。
    m_data_size--;

    // 從上到下(父節點到子樹結點)
    while (m_data_size > 1) {
        max_heapify(m_array, m_data_size, 1);
        swap(&m_array[1], &m_array[m_data_size]);
        m_data_size--;
    }
}

優先隊列

在計算機系統的作業調度中,任務需要根據優先級進行執行。可以根據堆算法(最大堆),每個任務都賦予一個優先級數值,選出最高優先級(最大堆堆頂)作業任務執行。涉及任務處理,一般都有增刪改查的操作。
理解了建堆,堆化和排序的流程,隊列的這些操作應該都比較好理解了。


HEAP-MAXINUM

獲取堆頂元素,時間複雜度爲O(1)O(1)
獲取堆頂元素

bool HeapSort::heap_maxinum(int& n) {
    if (m_data_size <= 0) {
        return false;
    }
    if (!m_is_build_heap) {
        build_max_heap(m_array, m_data_size);
    }
    n = m_array[1];
    return true;
}

HEAP-EXTRACT-MAX

刪除堆頂元素,時間複雜度爲O(n)O(n)
刪除堆頂元素

bool HeapSort::heap_extract_max(int& n) {
    if (m_data_size <= 0) {
        return false;
    }

    if (!m_is_build_heap) {
        build_max_heap(m_array, m_data_size);
    }

    n = m_array[1];
    swap(&m_array[1], &m_array[m_data_size]);
    m_data_size--;
    max_heapify(m_array, m_data_size, 1);
    return true;
}

HEAP-INCREAASE-KEY

增加堆指定元素(任務)的數值,HEAP-INCREAASE-KEY 時間複雜度爲O(lgn)O(lgn)
增加數值

bool HeapSort::heap_increase_key(int i, int key) {
    if (i < 1 || m_data_size <= 0 || key < m_array[i]) {
        return false;
    }

    if (!m_is_build_heap) {
        build_max_heap(m_array, m_data_size);
    }

    // 這裏跟 build_max_heap 道理一樣,只是 build_max_heap
    // 是自底向上,heap_increase_key 是從 i 結點向上
    m_array[i] = key;
    while (parent(i) > 0 && m_array[parent(i)] < m_array[i]) {
        swap(m_array[parent(i)], m_array[i]);
        i = parent(i);
    }
    return true;
}

MAX-HEAP-INSERT

插入新元素到最大堆末位,也就是在最大堆上增加一個葉子,葉子自下而上與它的父結點比較替換。運行時間複雜度爲O(lgn)O(lgn)
插入

bool HeapSort::max_heap_insert(int key) {
    if (m_data_size >= m_data_len) {
        return false;
    }

    if (!m_is_build_heap) {
        build_max_heap(m_array, m_data_size);
    }

    // 將結點放置數組末位,也就是在最大堆上增加一個葉子,葉子自下而上與它的父結點比較替換。
    m_data_size++;
    m_array[m_data_size] = key;

    // 這是 heap_increase_key 主邏輯的實現。
    int i = m_data_size;
    while (parent(i) > 0 && m_array[parent(i)] < m_array[i]) {
        swap(m_array[parent(i)], m_array[i]);
        i = parent(i);
    }

    return true;
}

參考

現在發現,單純看《算法導論》很難看懂文章內容,可以先從其它的帖子中理解算法的邏輯,在對算法有一定的理解的基礎上,再結合書本內容,才能更好理解書本內容。


wiki
《算法導論》第六章 堆排序
堆和堆排序:爲什麼說堆排序沒有快速排序快

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