程序員的進階課-架構師之路(17)-堆

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: http://www.javashuo.com/article/p-fxvhnvps-dt.html

 

咱們來介紹另一種數據結構-,注意這裏的堆和咱們Java語言,C++語言等編程語言在內存中的「堆」是不同的,這裏的堆是一種樹,由它實現的優先級隊列的插入和刪除的時間複雜度都爲O(logN),這樣儘管刪除的時間變慢了,可是插入的時間快了不少,當速度很是重要,並且有不少插入操做時,能夠選擇用堆來實現優先級隊列。html

1、堆的定義

【百度百科】堆(英語:heap)是計算機科學中一類特殊的數據結構的統稱。堆一般是一個能夠被看作一棵徹底二叉樹的數組對象。堆老是知足下列性質:程序員

  1. 堆中某個節點的值老是不大於或不小於其父節點的值;
  2. 堆老是一棵徹底二叉樹。

將根節點最大的堆叫作最大堆或大根堆,根節點最小的堆叫作最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。https://img.mukewang.com/5b28749e00015c4105580327.jpg面試

https://img.mukewang.com/5b28749e00015c4105580327.jpg

2、堆的一些實例

注意下面兩種狀況,b最後一層從左到右中間有斷隔,那麼也是不徹底二叉樹。全部a是堆,而b不是。編程

圖一是最大堆,圖二是最小堆:api

3、堆的實現方式

堆的經典實現方式:使用數組來存儲一個二叉堆,左節一次放大2倍,右節點則是2倍加1數組

https://img4.mukewang.com/5b2874b20001fd9705580309.jpg

4、堆的操做

堆不是容器,而是組織容器元素的一種特別方式。 只能肯定堆的範圍,即開始和結束迭代器指定的範圍。這意味着基本上沒法對對進行遍歷和查找微信

所以,堆這種組織彷佛很是接近無序,因此,堆的操做通常爲:數據結構

  • 快速的移除最大(或最小)節點;
  • 快速插入新的節點。

堆有兩個原始操做用於保證插入或刪除節點之後堆是一個有效的最大堆或者最小堆:架構

  • shiftUp(): 若是一個節點比它的父節點大(最大堆)或者小(最小堆),那麼須要將它同父節點交換位置。這樣是這個節點在數組的位置上升。
  • shiftDown(): 若是一個節點比它的子節點小(最大堆)或者大(最小堆),那麼須要將它向下移動。這個操做也稱做「堆化(heapify)」。

shiftUp 或者 shiftDown 是一個遞歸的過程,因此它的時間複雜度是 O(log n)併發

基於這兩個原始操做還有一些其餘的操做:

  • insert(value): 在堆的尾部添加一個新的元素,而後使用 shiftUp 來修復對。
  • remove(): 移除並返回最大值(最大堆)或者最小值(最小堆)。爲了將這個節點刪除後的空位填補上,須要將最後一個元素移到根節點的位置,而後使用 shiftDown 方法來修復堆。
  • removeAtIndex(index): 和 remove() 同樣,差異在於能夠移除堆中任意節點,而不只僅是根節點。當它與子節點比較位置不時無序時使用 shiftDown(),若是與父節點比較發現無序則使用 shiftUp()
  • replace(index, value):將一個更小的值(最小堆)或者更大的值(最大堆)賦值給一個節點。因爲這個操做破壞了堆屬性,因此須要使用 shiftUp() 來修復堆屬性。

上面全部的操做的時間複雜度都是 O(log n),由於 shiftUp 和 shiftDown 都很費時。還有少數一些操做須要更多的時間:

  • search(value):堆不是爲快速搜索而創建的,可是 replace()removeAtIndex() 操做須要找到節點在數組中的index,因此你須要先找到這個index。時間複雜度:O(n)
  • buildHeap(array):經過反覆調用 insert() 方法將一個(無序)數組轉換成一個堆。若是你足夠聰明,你能夠在 O(n) 時間內完成。
  • 堆排序:因爲堆就是一個數組,咱們可使用它獨特的屬性將數組從低到高排序。時間複雜度:O(n lg n)

堆還有一個 peek() 方法,不用刪除節點就返回最大值(最大堆)或者最小值(最小堆)。時間複雜度 O(1)

5、堆的用途

1.優先級隊列

Java中的類PriorityQueue,可參考: PriorityQueue使用

2.堆排序:適合處理數據量大的序列

堆排序就是利用堆刪除的方法,將第一個元素和最後一個元素交換,而後size-1,在將堆裏剩下的元素進行對調整,調整成一個新堆,一直到堆的size == 0,全部元素都已經排完了。

因爲它在直接選擇排序的基礎上利用了比較結果造成。效率提升很大。它完成排序的總比較次數爲O(nlog2n)。

堆排序須要兩個步驟,一個建堆,二是交換從新建堆。比較複雜,因此通常在小規模的序列中不合適,但對於較大的序列,將表現出優越的性能。

3.top K 問題 :在海量數據中找出最大的前k個元素

在大量數據中找出其中最大的前k個數:能夠利用堆,先將前k個數用來建堆,剩下的依次次與堆中第一個數比較(由於大堆中第一個數最大,小堆第一個數最小,這裏以小堆找前k個最大的數爲例),若是遇到比小頂堆的堆頂的值大的,將它放入堆中 最終的堆會是數組中最大的K個數組成的結構,在用堆排序就能夠將最大的數按順序排列。

6、內存分配中的堆區和棧區

內存中的堆和棧第一個區別就是申請方式的不一樣:棧是系統自動分配空間的,而堆則是程序員根據須要本身申請的空間。因爲棧上的空間是自動分配自動回收的,因此棧上的數據的生存週期只是在函數的運行過程當中,運行後就釋放掉,不能夠再訪問。而堆上的數據只要程序員不釋放空間,就一直能夠訪問到,不過缺點是一旦忘記釋放會形成內存泄露。

申請效率的比較:棧由系統自動分配,速度較快。但程序員是沒法控制的。堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便。

申請大小的限制:

棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在Windows下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。

堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。


個人微信公衆號:架構真經(id:gentoo666),分享Java乾貨,高併發編程,熱門技術教程,微服務及分佈式技術,架構設計,區塊鏈技術,人工智能,大數據,Java面試題,以及前沿熱門資訊等。每日更新哦!

參考資料:

  1. http://www.javashuo.com/article/p-zvgtzzgs-bo.html
  2. https://www.jianshu.com/p/5f148c3e4f7d
  3. https://www.imooc.com/article/details/id/37044
  4. http://www.javashuo.com/article/p-uptitppe-bz.html
  5. http://www.javashuo.com/article/p-dhlkgdxb-gc.html
  6. https://blog.csdn.net/A__B__C__/article/details/82832511
  7. https://blog.csdn.net/qq_34115899/article/details/79389066
  8. https://blog.csdn.net/qq_34115899/article/details/82314829
相關文章
相關標籤/搜索