排序算法之堆排序


堆排序


其他排序方法:選擇排序冒泡排序歸並排序快速排序插入排序希爾排序堆排序


概念

完全二叉樹

在講完全二叉樹之前,先引入完美二叉樹/滿二叉樹的概念。
每一個層的結點數都達到最大值的二叉樹就叫完美二叉樹。就像這樣:

而完全二叉樹的結點也像上圖的滿二叉樹那樣從上往下、從左到右進行編號的話,每個結點的位置都與滿二叉樹對應編號結點的位置相同。
也就是說,
如果最后一個葉子結點是其父親的右兒子,則除了葉子結點,每個結點必定有兩個兒子。
如果最后一個葉子結點是其父親的左兒子,則除了其父親與其它葉子結點,每個結點必定有兩個兒子。

是一個數組。它滿足兩個特點:

  • 完全二叉樹
  • 任一結點的值都是其子樹所有結點的最大值①或最小值②

情況①為最大堆

②為最小堆

我們這里主要講最大堆

存儲結構

堆和完全二叉樹我們通常用數組來存儲,

元素 a b c d e f g h i
下標 0 1 2 3 4 5 6 7 8

下標公式:
設父結點的下標為parent,左兒子的下標為leftChild,右兒子的下標為rightChild
\(parent = (leftChild-1)/2\)\(\lfloor (rightChild-1)/2 \rfloor\)
即,$$parent = \lfloor (child-1)/2 \rfloor$$

\[leftChild = parent*2+1 \]

\[rightChild = parent*2+2 \]

思想

堆排序其實就是利用堆的第二個特點:任一結點的值都是其子樹所有結點的最大值或最小值。
只要將需要排序的數組建立成堆,然后每次取出根結點,就把剩下的結點調整成堆;再取出根結點,如此下去,最后便能得到排好序的數據。

性能

堆排序的性能比較復雜,我們先看堆的建立,堆的建立有兩種方法:

  • 將元素一個一個地插入到空堆里,時間復雜度為O(NlogN)
  • 將元素按照完全二叉樹的結構存放到數組里,然后再調整各結點的位置,時間復雜度為O(N)

建好堆之后,開始排序,排序也有兩種方法:

  • 取出根結點的元素,把元素放進臨時的數組里,然后把剩下的結點調整成堆;重復前面的操作,最后臨時數組里的數據便排好序了
  • 直接在堆內部排序。先將根結點與最后一個結點的元素互換,然后將最后一個結點排除在外,進行堆調整;重復前面的操作,最后便排好序了

兩種方法時間復雜度均為O(NlogN),但第一種方法需要額外O(N)空間來進行輔助排序。

代碼

# 建立最大堆
def buildMaxHeap(heap):
    # 最后一個結點的下標
    lastChild = len(heap) - 1
    # 最后一個結點的父結點的下標
    parent = lastChild - 1 >> 1
    # 從最后一個結點的父結點開始往前遍歷結點
    # 並將以所遍歷到的結點為根結點的堆調整為最大堆
    while parent >= 0:
        percDown(heap, parent, lastChild)
        parent -= 1
# 將堆調整為最大堆
# 需要調整的堆的最后一個結點下標為lastChild
# 需要調整的堆的根結點下標為root
# percolate:過濾、滲透
def percDown(heap, root, lastChild):
    if root < 0 or root >= lastChild: return

    key = heap[root]
    parent = root
    # 只要parent有兒子,就繼續循環
    while parent << 1 < lastChild:
        # child指向parent的左兒子(parent*2+1)
        child = parent << 1 | 1
        # 如果右兒子比左兒子大,則child指向右兒子
        if child < lastChild and heap[child + 1] > heap[child]: child += 1

        # 如果根結點的值比兒子結點要小,則下濾
        if key < heap[child]:
            heap[parent] = heap[child]
        # 否則,位置適合,結束循環
        else:
            break
        # parent指向子結點,,調整以child為根的子堆
        parent = child
    # 如果發生了下濾,則將根結點的值移到合適的位置
    if parent != root:
        heap[parent] = key
# 返回最大堆的根結點元素,並將剩下結點調整為堆
def maxHeapPop(heap):
    # 最后一個結點的下標
    lastChild = len(heap) - 1

    if lastChild < 0: return

    # 取出根結點元素,將最后一個結點元素移到根結點
    maxItem, heap[0] = heap[0], heap[lastChild]
    # 堆元素少了一個
    lastChild -= 1
    heap.pop()

    # 將剩下結點調整為堆
    percDown(heap, 0, lastChild)

    return maxItem

方法一排序

# 堆排序
def heapSort1(heap):
    tmpHeap = heap.copy()
    # 建立最大堆
    buildMaxHeap(tmpHeap)
    # 取出根結點的元素,把元素放進臨時的數組里,然后把剩下的結點調整成堆
    for i in range(len(heap) - 1, -1, -1):
        heap[i] = maxHeapPop(tmpHeap)

方法二排序

# 堆排序
def heapSort2(heap):
    # 建立最大堆
    buildMaxHeap(heap)
    # 最后一個結點的下標
    lastChild = len(heap) - 1
    # 將最大值與最后一個結點的元素位置互換,然后將最后一個結點排除在外,進行堆調整;一直重復這一步,直到只剩一個根結點
    while lastChild > 0:
        heap[0], heap[lastChild] = heap[lastChild], heap[0]
        lastChild -= 1
        percDown(heap, 0, lastChild)

其他排序方法:選擇排序冒泡排序歸並排序快速排序插入排序希爾排序堆排序


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM