天天看點

[算法導論]堆排序堆參考

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

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

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

最大堆中,是指除了根結點外(根結點沒有 parent),所有結點的 i 都應該滿足:

A [ P A R E N T ( i ) ] ≥ A [ i ] A[PARENT(i)] \ge A[i] A[PARENT(i)]≥A[i]

[算法導論]堆排序堆參考

堆被看作是一個完全二叉樹,那麼該堆堆高度成正比O(lgn),我們會發現,堆結構上的一些基本操作的運作時間至多與樹的高度成正比,即實際複雜度為O(lgn)。

堆排序過程:

MAX-HEAPIFY(堆化):将堆的末端子節點作調整,使得子節點永遠小于父節點。

BUILD-MAX-HEAD(建堆):從無序資料數組中構造一個最大堆。

HEAPSORT(排序):對數組進行原址排序,移除位在第一個資料的根節點,并做最大堆調整的遞歸運算。

堆化

堆化讓數值大的結點往上浮,讓小的結點逐漸下沉。

[算法導論]堆排序堆參考
[算法導論]堆排序堆參考
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) 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) 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 ( l g n ) 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 ( l g n ) 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

《算法導論》第六章 堆排序

堆和堆排序:為什麼說堆排序沒有快速排序快

繼續閱讀