堆:一種特殊的徹底二叉樹結構。python
大根堆:一棵徹底二叉樹,知足任一節點都比其孩子節點大;算法
小根堆:一棵徹底二叉樹,知足任一節點都比其餘孩子節點小。api
假設:節點的左右子樹都是堆,但自身不是堆。app
因爲左右子樹都是大根堆,可是2並不比其孩子節點大,所以2不稱職,須要更換新的領導dom
2也不夠資格作八、5的父節點,繼續下移,8提上來作父節點:函數
2也不夠資格作六、4的父節點,將6提上來作父節點,2放到6原來的位置,成爲葉子節點:網站
當根節點的左右子樹都是堆時(根節點不知足堆的性質),能夠經過一次向下的調整來將其變換成一個堆。spa
一、創建堆設計
二、獲得堆頂元素爲最大元素3d
三、去掉堆頂,將堆最後一個元素放到堆頂,此時可經過一次調整從新使堆有序。
四、堆頂元素爲第二大元素。
五、重複步驟3,知道堆變空
(1)以下圖所示爲一個堆,9爲堆頂元素,也是堆的最大元素
(2)去除堆頂元素9,將堆最後元素3放到堆頂
(3)此時知足了向下調整的條件,用向下調整以保證仍爲一個堆(徹底二叉樹)
(4)此時堆頂元素8是第二大元素,再次去除堆頂元素8,再次將3提到堆頂。
(5)再次知足向下調整的條件,作向下調整,依此類推。
如上圖所示的二叉樹不符合堆的結構特徵,因爲向下調整的性質,構造堆首先要讓下級先有序。
(1)若是有不少層怎麼看?看最後一個非葉子節點!對子樹作一次調整
(2)再看前一個非葉子節點,該子樹符合堆的結構特色所以不作調整
(3)再看前一個非葉子節點,該子樹不符合堆結構,進行子樹調整
(4)再觀察前一個非葉子節點,以總體做爲子樹調整
(5)到這一步以後就又開始了向下調整,堆也就構造完成了
在實際實現中爲了最大節省空間和時間,並不會從新生成一個空間存放堆頂元素。而是將堆頂元素(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放在葉子節點上
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,堆中最後一個元素
首先sift函數最可能是走一個樹的高度層(走左邊右邊就不用考慮),所以它的時間複雜度是logn。
因而可知heap_sort是2個nlogn,所以堆排序的時間複雜度是nlogn級別。
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, """
如今有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)
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] """