python數據結構與算法-15_堆與堆排序

堆(heap)

前面我們講了兩種使用分治和遞歸解決排序問題的歸并排序和快速排序,中間又穿插了一把樹和二叉樹,
本章我們開始介紹另一種有用的數據結構堆(heap), 以及借助堆來實現的堆排序,相比前兩種排序算法要稍難實現一些。
最后我們簡單提一下 python 標準庫內置的 heapq 模塊。

什么是堆?

堆是一種完全二叉樹(請你回顧下上一章的概念),有最大堆和最小堆兩種。

  • 最大堆: 對于每個非葉子節點 V,V 的值都比它的兩個孩子大,稱為 最大堆特性(heap order property)
    最大堆里的根總是存儲最大值,最小的值存儲在葉節點。
  • 最小堆:和最大堆相反,每個非葉子節點 V,V 的兩個孩子的值都比它大。
    在這里插入圖片描述

堆的操作

堆提供了很有限的幾個操作:

  • 插入新的值。插入比較麻煩的就是需要維持堆的特性。需要 sift-up 操作,具體會在視頻和代碼里解釋,文字描述起來比較麻煩。
  • 獲取并移除根節點的值。每次我們都可以獲取最大值或者最小值。這個時候需要把底層最右邊的節點值替換到 root 節點之后
    執行 sift-down 操作。

在這里插入圖片描述

在這里插入圖片描述

堆的表示

上一章我們用一個節點類和二叉樹類表示樹,這里其實用數組就能實現堆。

在這里插入圖片描述

仔細觀察下,因為完全二叉樹的特性,樹不會有間隙。對于數組里的一個下標 i,我們可以得到它的父親和孩子的節點對應的下標:

parent = int((i-1) / 2)    # 取整
left = 2 * i + 1
right = 2 * i + 2

超出下標表示沒有對應的孩子節點。

實現一個最大堆

我們將在視頻里詳細描述和編寫各個操作

class MaxHeap(object):def __init__(self, maxsize=None):self.maxsize = maxsizeself._elements = Array(maxsize)self._count = 0def __len__(self):return self._countdef add(self, value):if self._count >= self.maxsize:raise Exception('full')self._elements[self._count] = valueself._count += 1self._siftup(self._count-1)  # 維持堆的特性def _siftup(self, ndx):if ndx > 0:parent = int((ndx-1)/2)if self._elements[ndx] > self._elements[parent]:    # 如果插入的值大于 parent,一直交換self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]self._siftup(parent)    # 遞歸def extract(self):if self._count <= 0:raise Exception('empty')value = self._elements[0]    # 保存 root 值self._count -= 1self._elements[0] = self._elements[self._count]    # 最右下的節點放到root后siftDownself._siftdown(0)    # 維持堆特性return valuedef _siftdown(self, ndx):left = 2 * ndx + 1right = 2 * ndx + 2# determine which node contains the larger valuelargest = ndxif (left < self._count and     # 有左孩子self._elements[left] >= self._elements[largest] andself._elements[left] >= self._elements[right]):  # 原書這個地方沒寫實際上找的未必是largestlargest = leftelif right < self._count and self._elements[right] >= self._elements[largest]:largest = rightif largest != ndx:self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]self._siftdown(largest)def test_maxheap():import randomn = 5h = MaxHeap(n)for i in range(n):h.add(i)for i in reversed(range(n)):assert i == h.extract()

實現堆排序

上邊我們實現了最大堆,每次我們都能 extract 一個最大的元素了,于是一個倒序排序函數就能很容易寫出來了:

def heapsort_reverse(array):length = len(array)maxheap = MaxHeap(length)for i in array:maxheap.add(i)res = []for i in range(length):res.append(maxheap.extract())return resdef test_heapsort_reverse():import randoml = list(range(10))random.shuffle(l)assert heapsort_reverse(l) == sorted(l, reverse=True)

Python 里的 heapq 模塊

python 其實自帶了 heapq 模塊,用來實現堆的相關操作,原理是類似的。請你閱讀相關文檔并使用內置的 heapq 模塊完成堆排序。
一般我們刷題或者寫業務代碼的時候,使用這個內置的 heapq 模塊就夠用了,內置的實現了是最小堆。

Top K 問題

面試題中有這樣一類問題,讓求出大量數據中的top k 個元素,比如一億個數字中最大的100個數字。
對于這種問題有很多種解法,比如直接排序、mapreduce、trie 樹、分治法等,當然如果內存夠用直接排序是最簡單的。
如果內存不夠用呢? 這里我們提一下使用固定大小的堆來解決這個問題的方式。

一開始的思路可能是,既然求最大的 k 個數,是不是應該維護一個包含 k 個元素的最大堆呢?
稍微嘗試下你會發現走不通。我們先用數組的前面 k 個元素建立最大堆,然后對剩下的元素進行比對,但是最大堆只能每次獲取堆頂
最大的一個元素,如果我們取下一個大于堆頂的值和堆頂替換,你會發現堆底部的小數一直不會被換掉。如果下一個元素小于堆頂
就替換也不對,這樣可能最大的元素就被我們丟掉了。

相反我們用最小堆呢?
先迭代前 k 個元素建立一個最小堆,之后的元素如果小于堆頂最小值,跳過,否則替換堆頂元素并重新調整堆。你會發現最小堆里
慢慢就被替換成了最大的那些值,并且最后堆頂是最大的 topk 個值中的最小值。
(比如1000個數找10個,最后堆里剩余的是 [990, 991, 992, 996, 994, 993, 997, 998, 999, 995],第一個 990 最小)

按照這個思路很容易寫出來代碼:

import heapqclass TopK:"""獲取大量元素 topk 大個元素,固定內存思路:1. 先放入元素前 k 個建立一個最小堆2. 迭代剩余元素:如果當前元素小于堆頂元素,跳過該元素(肯定不是前 k 大)否則替換堆頂元素為當前元素,并重新調整堆"""def __init__(self, iterable, k):self.minheap = []self.capacity = kself.iterable = iterabledef push(self, val):if len(self.minheap) >= self.capacity:min_val = self.minheap[0]if val < min_val:  # 當然你可以直接 if val > min_val操作,這里我只是顯示指出跳過這個元素passelse:heapq.heapreplace(self.minheap, val)  # 返回并且pop堆頂最小值,推入新的 val 值并調整堆else:heapq.heappush(self.minheap, val)  # 前面 k 個元素直接放入minheapdef get_topk(self):for val in self.iterable:self.push(val)return self.minheapdef test():import randomi = list(range(1000))  # 這里可以是一個可迭代元素,節省內存random.shuffle(i)_ = TopK(i, 10)print(_.get_topk())  # [990, 991, 992, 996, 994, 993, 997, 998, 999, 995]if __name__ == '__main__':test()

源碼

# python3
class MinHeap:def __init__(self):"""這里提供一個最小堆實現。如果面試不讓用內置的堆非讓你自己實現的話,考慮用這個簡版的最小堆實現。一般只需要實現 heqppop,heappush 兩個操作就可以應付面試題了parent: (i-1)//2。注意這么寫 int((n-1)/2), python3 (n-1)//2當n=0結果是-1而不是0left:  2*i+1right: 2*i+2參考:https://favtutor.com/blogs/heap-in-pythonhttps://runestone.academy/ns/books/published/pythonds/Trees/BinaryHeapImplementation.htmlhttps://www.askpython.com/python/examples/min-heap"""self.pq = []def min_heapify(self, nums, k):"""遞歸調用,維持最小堆特性"""l = 2*k+1  # 左節點位置r = 2*k+2  # 右節點if l < len(nums) and nums[l] < nums[k]:smallest = lelse:smallest = kif r < len(nums) and nums[r] < nums[smallest]:smallest = rif smallest != k:nums[k], nums[smallest] = nums[smallest], nums[k]self.min_heapify(nums, smallest)def heappush(self, num):"""列表最后就加入一個元素,之后不斷循環調用維持堆特性"""self.pq.append(num)n = len(self.pq) - 1# 注意必須加上n>0。因為 python3 (n-1)//2 當n==0 的時候結果是-1而不是0!while n > 0 and self.pq[n] < self.pq[(n-1)//2]:  # parent 交換self.pq[n], self.pq[(n-1)//2] = self.pq[(n-1)//2], self.pq[n]  # swapn = (n-1)//2def heqppop(self):  # 取 pq[0],之后和pq最后一個元素pq[-1]交換之后調用 min_heapify(0)minval = self.pq[0]last = self.pq[-1]self.pq[0] = lastself.min_heapify(self.pq, 0)self.pq.pop()return minvaldef heapify(self, nums):n = int((len(nums)//2)-1)for k in range(n, -1, -1):self.min_heapify(nums, k)def test_MinHeqp():import randoml = list(range(1, 9))random.shuffle(l)pq = MinHeap()for num in l:pq.heappush(num)res = []for _ in range(len(l)):res.append(pq.heqppop())  # 利用 heqppop,heqppush 實現堆排序def issorted(l): return all(l[i] <= l[i+1] for i in range(len(l) - 1))assert issorted(res)

練習題

  • 這里我用最大堆實現了一個 heapsort_reverse 函數,請你實現一個正序排序的函數。似乎不止一種方式
  • 請你實現一個最小堆,你需要修改那些代碼呢?
  • 我們實現的堆排序是 inplace 的嗎,如果不是,你能改成 inplace 的嗎?
  • 堆排序的時間復雜度是多少? siftup 和 siftdown 的時間復雜度是多少?(小提示:考慮樹的高度,它決定了操作次數)
  • 請你思考 Top K 問題的時間復雜度是多少?

延伸閱讀

  • 《算法導論》第 6 章 Heapsort
  • 《Data Structures and Algorithms in Python》 13.5 節 Heapsort
  • 閱讀 Python heapq 模塊的文檔

Leetcode

合并 k 個有序鏈表 https://leetcode.com/problems/merge-k-sorted-lists/description/

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/162693.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/162693.shtml
英文地址,請注明出處:http://en.pswp.cn/news/162693.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux開發工具(含gdb調試教程)

文章目錄 Linux開發工具&#xff08;含gdb調試教程&#xff09;1、Linux 軟件包管理器 yum2、Linux開發工具2.1、Linux編輯器 -- vim的使用2.1.1、vim的基本概念2.1.2、vim的基本操作2.1.3、vim正常模式命令集2.1.4、vim末行模式命令集 2.2、vim簡單配置 3、Linux編譯器 -- gcc…

HIVE SQL取整函數匯總

目錄 int()round(double a)round(double a,int d)floor()ceil() int() 向零取整&#xff0c;即向接近零的方向取整。 int(5.6)輸出&#xff1a;5 int(-5.6)輸出&#xff1a;-5 round(double a) 四舍五入取整 select round(5.6)輸出&#xff1a;6 select round(-5.6)輸出&…

關于前端處理后端輪詢的操作 (總結)

使用場景&#xff1a;前端首次發起請求獲取數據&#xff0c;若失敗則每隔1s發起一次知道成功獲取數據為止解決方案&#xff1a; 使用輪詢操作&#xff0c;涉及定時器的使用和關閉 &#xff08;使用vue2代碼為例) data() {return {pollingResult_en: null, // 處理輪詢結果bizI…

redis之cluster集群

1、redis-cluster集群&#xff1a;redis3.0引入的分布式存儲方案 2、集群&#xff1a;由多個node節點組成&#xff0c;redis數據分布在這些節點之中 &#xff08;1&#xff09;在集群之中也分主節點和從節點 &#xff08;2&#xff09;自帶哨兵模式 3、redis-cluster集群的…

騰訊云 小程序 SDK對象存儲 COS使用記錄,原生小程序寫法。

最近做了一個項目&#xff0c;需求是上傳文檔&#xff0c;文檔類型多種&#xff0c;圖片&#xff0c;視頻&#xff0c;文件&#xff0c;doc,xls,zip,txt 等等,而且文檔類型可能是大文件&#xff0c;可能得上百兆&#xff0c;甚至超過1G。 騰訊云文檔地址&#xff1a;https://c…

Java接口自動化測試系列[V1.0.0][概述]

基礎知識 在TCP/IP中&#xff0c;HTTP屬于傳輸層協議&#xff0c;該協議采用的是Request-Response的模式&#xff0c;且該協議是無狀態的&#xff0c;也就是后續如果要用到前面的信息必須重新請求重新獲取&#xff1b;HTTP通過SSL/TSL加密成為HTTPS&#xff0c;與HTTP相比HTTP…

PC端頁面進去先出現加載效果

自定義指令v-loading&#xff0c;只需要綁定Boolean即可 v-loading“loading” <el-table :data"list" border style"width: 100%" v-loading"loading"><el-table-column align"center" label"序號" width"5…

開發板啟動進入系統以后再掛載 NFS 文件系統, 這里的NFS文件系統是根據正點原子教程制作的ubuntu_rootfs

如果是想開發板啟動進入系統以后再掛載 NFS 文件系統&#xff0c;開發板啟動進入文件系統&#xff0c;開發板和 ubuntu 能互相 ping 通&#xff0c;在開發板文件系統下新建一個目錄 you&#xff0c;然后執行如下指令進行掛載&#xff1a; mkdir mi mount -t nfs -o nolock,nfsv…

Hive日志默認存儲在什么位置?

在hive-log4j.properties配置文件中&#xff0c;有這么一段配置信息 hive.log.thresholdALL hive.root.loggerWARN,DRFA hive.log.dir${java.io.tmpdir}/${user.name} hive.log.filehive.log hive.log.dir就是日志存儲在目錄/tmp/${user.name}(當前用戶名)/下 而hive.log就是h…

日本it就職培訓機構,日本IT行業的三種類型

日本的IT產業一直保持增長趨勢&#xff0c;市場規模逐年增加&#xff0c;在日本所有產業中占據很大比例。由于日本老齡化嚴重&#xff0c;日本國內的IT人才無法滿足需求&#xff0c;為緩解這一問題&#xff0c;日本將引進外國優秀IT人才作為一項國策&#xff0c;日本IT行業不僅…

Leetcode1410. HTML 實體解析器

Every day a Leetcode 題目來源&#xff1a;1410. HTML 實體解析器 解法1&#xff1a;模擬 遍歷字符串 text&#xff0c;每次遇到 ’&‘&#xff0c;就判斷以下情況&#xff1a; 雙引號&#xff1a;字符實體為 &quot; &#xff0c;對應的字符是 " 。單引號&a…

振弦式土壓力計在巖土工程安全監測應用的方案

振弦式土壓力計在巖土工程安全監測應用的方案 振弦式土壓力計是一種常見的土壓力測量儀器&#xff0c;其原理是利用振弦在土中傳播的速度與土的應力狀態有關的特點測量土壓力。在巖土工程安全監測中&#xff0c;振弦式土壓力計可以應用于以下方面&#xff1a; 1. 地下連續墻和…

某資產管理機構: IAST提升安全水平,保障資產管理水平穩健增長

某資產管理機構是國內首批成立的資產管理公司之一&#xff0c;堅持“科技金融”、“數字金融”戰略&#xff0c;以客戶為中心&#xff0c;聚焦用戶體驗與業務協同&#xff0c;著力推進營銷數字化進程和大數據平臺建設&#xff0c;助力資產管理高質量發展。 數字科技推動工作效率…

面試題:Java 對象不使用時,為什么要賦值 null ?

文章目錄 前言示例代碼運行時棧典型的運行時棧Java的棧優化提醒 GC一瞥提醒 JVM的“BUG”總結 前言 最近&#xff0c;許多Java開發者都在討論說&#xff0c;“不使用的對象應手動賦值為null“ 這句話&#xff0c;而且好多開發者一直信奉著這句話&#xff1b;問其原因&#xff…

【Flask使用】全知識md文檔,4大部分60頁第3篇:Flask模板使用和案例

本文的主要內容&#xff1a;flask視圖&路由、虛擬環境安裝、路由各種定義、狀態保持、cookie、session、模板基本使用、過濾器&自定義過濾器、模板代碼復用&#xff1a;宏、繼承/包含、模板中特有變量和函數、Flask-WTF 表單、CSRF、數據庫操作、ORM、Flask-SQLAlchemy…

nvm切換版本之后npm用不了

原因是 nvm只給你安了對應的node沒給你安裝對應的node版本的npm 解決辦法如下 1找到你安裝的node版本號 然后去官網下載對應的版本包 這個網址就是node官網的版本列表 Index of /download/release/ 2下載后解壓 把根目錄這倆復制到自己的nvm安裝目錄下 還有那個node_modul…

Java【XML 配置文件解析】

前言 最近考試周忙得要死&#xff0c;但我卻不緊不慢&#xff0c;還有三天復習時間&#xff0c;考試科目幾乎都還沒學呢。今天更新一個算是工具類-XML文件的解析&#xff0c;感覺還是挺有用的&#xff0c;之后可以融進自己的項目里。 XML 配置文件解析 0、導入依賴 有點像我…

海康攝像頭ip地址設置方法

海康攝像頭是當前市場上非常受歡迎的一種監控設備&#xff0c;其可以在各種場合下發揮出極佳的作用。不過&#xff0c;對于初次使用該設備的人來說&#xff0c;設置其ip地址往往比較困難。虎觀代理小二二將會詳細介紹海康攝像頭ip地址設置的具體步驟&#xff0c;幫助大家輕松解…

PS右邊的圖層窗口沒有顯示出來?

問題描述&#xff1a;PS右邊的圖層窗口沒有顯示出來&#xff1f; 解決步驟&#xff1a; 鍵盤F7快捷鍵即可調出來。

企業軟件定制開發的優勢|app小程序網站搭建

企業軟件定制開發的優勢|app小程序網站搭建 企業軟件定制開發是一種根據企業特定需求開發定制化軟件的服務。相比于購買現成的軟件產品&#xff0c;企業軟件定制開發具有許多優勢。 1.企業軟件定制開發可以滿足企業獨特需求。每個企業都有自己獨特的業務流程和需求&#xff0c;…