算法——堆和堆排序介紹

1、什麼是堆?

  堆:一種特殊的徹底二叉樹結構。python

  

  大根堆:一棵徹底二叉樹,知足任一節點都比其孩子節點算法

  小根堆:一棵徹底二叉樹,知足任一節點都比其餘孩子節點api

2、堆的向下調整性質

  假設:節點的左右子樹都是堆,但自身不是堆。app

   

一、圖示向下調整過程

  因爲左右子樹都是大根堆,可是2並不比其孩子節點大,所以2不稱職,須要更換新的領導dom

  

  2也不夠資格作八、5的父節點,繼續下移,8提上來作父節點:函數

  

  2也不夠資格作六、4的父節點,將6提上來作父節點,2放到6原來的位置,成爲葉子節點:網站

  

二、堆向下調整總結

  當根節點的左右子樹都是堆時(根節點不知足堆的性質),能夠經過一次向下的調整來將其變換成一個堆。spa

3、堆排序

一、堆排序過程

  一、創建堆設計

  二、獲得堆頂元素爲最大元素3d

  三、去掉堆頂,將堆最後一個元素放到堆頂,此時可經過一次調整從新使堆有序。

  四、堆頂元素爲第二大元素。

  五、重複步驟3,知道堆變空

二、堆排序過程——挨個出數圖示

  (1)以下圖所示爲一個堆,9爲堆頂元素,也是堆的最大元素

  

  (2)去除堆頂元素9,將堆最後元素3放到堆頂

  

  (3)此時知足了向下調整的條件,用向下調整以保證仍爲一個堆(徹底二叉樹)

  

  (4)此時堆頂元素8是第二大元素,再次去除堆頂元素8,再次將3提到堆頂。

  

  (5)再次知足向下調整的條件,作向下調整,依此類推。

 

   

三、堆排序過程——構造堆圖示

  

   如上圖所示的二叉樹不符合堆的結構特徵,因爲向下調整的性質,構造堆首先要讓下級先有序。

  (1)若是有不少層怎麼看?看最後一個非葉子節點!對子樹作一次調整

   

  (2)再看前一個非葉子節點,該子樹符合堆的結構特色所以不作調整

  

  (3)再看前一個非葉子節點,該子樹不符合堆結構,進行子樹調整

  

  (4)再觀察前一個非葉子節點,以總體做爲子樹調整

  

  (5)到這一步以後就又開始了向下調整,堆也就構造完成了

  

4、堆排序代碼實現

  

  在實際實現中爲了最大節省空間和時間,並不會從新生成一個空間存放堆頂元素。而是將堆頂元素(9)和最後一個元素(3)進行交換。並標記9這個元素不在堆內,只是佔用了一個位置,標記元素(4)是堆的最後一個元素。

一、向下調整函數的實現 

def sift(li, low, high):
    """
    向下調整函數
    :param li:列表
    :param low:堆的根節點位置
    :param high:堆的最後一個元素的位置
    :return:
    """
    i = low     # 父節點位置(編號下標)最開始指向根節點(0)
    j = 2 * i + 1    # 子節點位置(左孩子節點編號下標爲2i+1)
    tmp = li[low]    # 把堆頂存起來
    while j<= high:    # 只要j位置有值就一直循環(保證不越界)
        if j<= high and li[j+1] > li[j]:   # 若是右孩子存在而且大於左孩子
            j = j + 1  # 將j指向右孩子
        if li[j] > tmp:   # 若是下標j節點元素大於堆頂元素
            li[i] = li[j]   # 將j位置上的數寫到i位置(空位置)上
            i = j   # 再往下看一層
            j = 2 * i +1   # j指向下一層的左子孩子
        else:   # 若是tmp更大,將tmp放到i的位置上
            li[i] = tmp   # 循環跳出條件一:tmp放到了某一個父節點位置上
            break
    else:   # 循環跳出條件二:j>high  ,此時i已經指向了葉子節點,i不存在子節點了
        li[i] = tmp   # 將tmp放在葉子節點上

二、使用sift函數實現堆排序 

def heap_sort(li):
    n = len(li)
    """建堆"""
    for i in range((n-2)//2, -1, -1):  # i從n-2整除2開始倒着遍歷到0,一個一個子樹調整
        # i表示建堆的時候調整的部分根的下標。
        sift(li, i, n-1)
    """挨個出數"""
    for i in range(n-1, -1, -1):   # i從n-1開始一直到零
        # i指向當前堆的最後一個元素
        li[0], li[i] = li[i], li[0]   # 堆頂(li[0])和最後一個元素(li[i])交換位置
        sift(li, 0, i-1)   # i-1是新的high,堆中最後一個元素  

5、堆排序時間複雜度

  首先sift函數最可能是走一個樹的高度層(走左邊右邊就不用考慮),所以它的時間複雜度是logn。

  因而可知heap_sort是2個nlogn,所以堆排序的時間複雜度是nlogn級別。

6、python堆排序內置模塊(heapq)

import heapq   # q——》queue優先隊列
import random

li = list(range(10))
random.shuffle(li)

print(li)

heapq.heapify(li)    # 建堆
print(li)

n = len(li)
for i in range(n):
    print(heapq.heappop(li), end=',')    # 每次彈出最小元素

"""
[3, 4, 7, 6, 2, 5, 1, 0, 8, 9]
[0, 2, 1, 4, 3, 5, 7, 6, 8, 9]
0,1,2,3,4,5,6,7,8,9,
"""

7、topk問題(堆應用)

一、什麼是topk問題?

  如今有n個數,設計算法獲得前k大的數。(k<n)

  經常使用於實現網站熱搜榜等。

二、解決思路

(1)排序後切片:O(nlogn)

(2)排序LowB三人組:O(kn)

(3)堆排序的思路:O(nlogk)

  取列表前k個元素創建一個小根堆。堆頂就是目前第k大的數(最小的數)。

  依次向後遍歷原列表,對於列表中的元素,若是小於堆頂,則忽略該元素;若是大於堆頂,則將堆頂更換爲該元素,而且對堆進行依次調整。

  遍歷列表全部元素後,倒序彈出堆頂。

三、堆排序思路圖解

  好比要從如下這十個數中取前五大的數:

   

  先取前五個數創建一個小根堆:

  

  如今堆頂1就是小根堆中第五大的數,下一個數是0,比1還要小,直接排除。

  再下一個數是7,7比1大,所以7把1換掉:

  

  小根堆向下調整:

  

  接着看2,2比3小,直接排除,4比3大替換3,5比4大替換4.均不須要作向下調整:

  

  這樣就獲得了前5大的數。它仍是須要遍歷全部的數來判斷每一個數是否進堆(O(n)),同時堆的大小是k,所以調整的複雜度是O(logk)。因此總的時間複雜度是O(nlogk)

四、基於堆排序的topk代碼實現

def sift(li, low, high):
    """
    向下調整函數  (小根堆)
    :param li:列表
    :param low:堆的根節點位置
    :param high:堆的最後一個元素的位置
    :return:
    """
    i = low     # 父節點位置(編號下標)最開始指向根節點(0)
    j = 2 * i + 1    # 子節點位置(左孩子節點編號下標爲2i+1)
    tmp = li[low]    # 把堆頂存起來
    while j<= high:    # 只要j位置有值就一直循環(保證不越界)
        # if j+1 <= high and li[j+1] > li[j]:   # 若是右孩子存在而且大於左孩子
        if j + 1 <= high and li[j + 1] < li[j]:  # 取兩個孩子裏小的那個
            j = j + 1  # 將j指向右孩子
        # if li[j] > tmp:   # 若是下標j節點元素大於堆頂元素
        if li[j] < tmp:   # 只要小於省長就放過來,知足父親比孩子小
            li[i] = li[j]   # 將j位置上的數寫到i位置(空位置)上
            i = j   # 再往下看一層
            j = 2 * i +1   # j指向下一層的左子孩子
        else:   # 若是tmp更大,將tmp放到i的位置上
            li[i] = tmp   # 循環跳出條件一:tmp放到了某一個父節點位置上
            break
    else:   # 循環跳出條件二:j>high  ,此時i已經指向了葉子節點,i不存在子節點了
        li[i] = tmp   # 將tmp放在葉子節點上


def topk(li, k):
    heap = li[0:k]
    for i in range((k-2)//2, -1, -1):   # i從k-2整除2開始倒着遍歷到-1
        sift(heap, i, k-1)
    # 1.建堆
    for i in range(k, len(li)-1):
        if li[i] > heap[0]:
            heap[0] = li[i]  # 用li[i]覆蓋heap[0]的值
            sift(heap, 0, k-1)  # 將小根堆作一次調整
    # 2.遍歷heap
    for i in range(k-1, -1, -1):   # i從k-1開始一直到零
        # i指向當前堆的最後一個元素
        heap[0], heap[i] = heap[i], heap[0]   # 堆頂(li[0])和最後一個元素(li[i])交換位置
        sift(heap, 0, i-1)   # i-1是新的high,堆中最後一個元素
    # 3.出數
    return heap


li = list(range(100))
import random
random.shuffle(li)
print(li)
print(topk(li, 5))
"""
[28, 82, 65, 98, 54, 47, 79, 46, 19, 85, 26, 52, 69, 97, 91, 36, 81, 58, 87, 50, 24, 3, 17, 35, 39, 94, 11, 90, 74, 48, 68, 8, 7, 77, 57, 6, 44, 40, 14, 86, 23, 30, 45, 89, 31, 96, 9, 93, 84, 20, 15, 22, 67, 34, 66, 71, 59, 73, 41, 92, 63, 55, 12, 10, 99, 21, 49, 2, 4, 29, 0, 70, 51, 32, 27, 64, 76, 38, 53, 56, 61, 5, 62, 13, 78, 25, 18, 88, 16, 60, 83, 72, 43, 33, 80, 75, 1, 37, 95, 42]
[99, 98, 97, 96, 95]
"""
相關文章
相關標籤/搜索