算法導論知識梳理(三):數據結構

基本數據結構

基本數據結構包括棧、隊列、鏈表、有根樹。對於這幾個,你們應該都很熟悉,前三個就不做討論,只稍微講一下有根樹。樹大體有如下幾種:二叉樹,堆,B樹,字典樹。而咱們較爲熟悉的就是二叉樹,同時二叉樹也能夠推廣至n叉樹。可是不少狀況下,孩子結點個數都是不固定的,如對DOM的描述。因此對於分支無限的有根樹來講,更多會採用的是左孩子右兄弟的表示法進行描述,以下圖:數組

對於這種結構,我直接就想到了React Fiber:數據結構

Fiber = {
  stateNode, // 當前節點實例
  child, // 子節點,等同於left
  sibling, // 兄弟節點,等同於right
  return, // 父節點
  ...
};
複製代碼

不過不知道爲啥,以前看過的一些關於Fiber的文章都說這是鏈表結構,這明明就是分支無限的有根樹啊,有木有!函數

直接尋址表

當關鍵字的的全域U比較小時,直接尋址是一種簡單而有效的技術。舉個例子:爲了描述某一組數據,若是這些數據的關鍵字只多是0-10之間的某個天然數,某次的獲得的數據的關鍵字爲二、三、五、8,那麼這張表的結構就以下圖所示:性能

其缺點也很明顯:若是全域U很大,則須要用很大一張表T來存儲,明顯是不實際的。並且實際存儲的關鍵字集合K相對U來講可能很小,就使得分配給T的空間會有很大一部分都被浪費,因此就須要用到下面的散列表。學習

散列表

散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。spa

說白了,散列表就是實現字典操做的一種有效的數據結構,是對於數組結構的推廣,故其查找性能是十分好的。在一些合理的假設下,在散列表中查找一個元素的平均時間是O(1)。設計

散列表的大小m通常要比|U|小得多,一個具備關鍵字k的元素被散列到槽h(k)上,也能夠說h(k)是關鍵字k的散列值。散列函數縮小了數組下標的範圍,即減少了數組的大小,使其由|U|減小爲m。以下圖:指針

當兩個關鍵字被映射到同一個槽中時,這種情形叫衝突。解決衝突的方法通常有如下兩種:連接法和開放尋址法。code

連接法解決衝突

連接法就是將散列到同一個槽中的全部元素都放在一個鏈表中,以下圖所示:cdn

簡單分析:

散列表的裝填因子α=n/m。α越大,填入表中的元素越多,越容易產生衝突;反之則越不容易產生衝突。

假設已有散列表T,存放了n個元素,具備m個槽,那麼:

  1. 在最壞狀況下,即全部元素都在同一個槽中,散列表其實退化爲了鏈表,查找的時間爲O(n)
  2. 在平均狀況下,假設散列函數h將全部元素都均勻散列在每個槽中(這種假設被稱爲簡單均勻散列),即每一個槽中的元素爲n/m,其查找的所需的時間爲O(1 + α)(1指找到槽的時間,α指在鏈表中查找到目標元素的時間)。若是散列表中的槽數m與散列元素n成正比,即n=O(m),那麼α=n/m=O(m)/m=O(1),那麼查找所需的時間也只須要O(1 + α)=O(1)

開放尋址法解決衝突

在開放尋址法中,全部的元素都存在在散列表裏。也就是說,每一個表項或包含動態集合的一個元素,或包含NIL(無值)。開放尋址法的好處在於不用指針,而是計算出要存儲的槽序列。因爲不用存儲指針,就能夠用省下來的空間存儲更多的槽,潛在地減小了衝突,提升了檢索速度。可是在開放尋址法中,散列表可能被填滿,以致於沒法插入任何新的元素。

爲了使用開放尋址法插入一個元素,須要連續地檢查散列表,這個過程被稱爲探查。

假設全局U經過散列函數散列後變成{0,1,...,m-1},即h:U*{0,1,...,m-1} -> {0,1,...,m-1}

對於每個關鍵字k,其使用開放尋址法的探查序列<h(k, 0), h(k, 1),...,h(k,m-1)>是<0,1,...,m-1>的某一種排序。若是每一個關鍵字的探查序列等可能爲<0,1,...,m-1>的m!種排序的任意一種,這種狀況稱爲均勻散列假設。均勻散列其實就是將前面所說的簡單均勻散列的概念加了通常化,推廣到了散列函數的結果不僅是一個數,而是一個完整的探查序列。

說白了,開放尋址法就是根據探查序列來決定插入元素位置的操做。舉個例子:若散列表T具備3個槽,分別爲0、一、2,某個元素k的探查序列爲<2,0,1>,在插入元素k時,會先檢查槽T[2]是否爲NIL,若是爲空,則將k插入槽T[2]的位置;不然,再去檢查槽T[0],執行上訴檢查。若檢查到槽T[1]時,也不爲空,則說明此散列表已滿,沒法插入。

線性探查

給定一個散列函數h':U -> {0,1,...,m-1},稱之爲輔助散列函數,線性探查所採用的散列函數爲h(k,i)=(h'(k)+i) mod m, i=0,1,...,m-1。

意思就是,對元素k進行插入時,先檢查槽T[h'(k)],即輔助散列函數給出的槽位。再探查槽T[h'(k)+1],直至探查到槽T[m-1]。而後又繞到槽T[0],T[1],...T[h'(k)-1]。線性探查方法比較容易實現,可是它存在一次羣集的問題。隨着連續被佔用的槽不斷增長,平均查找時間也會不斷增長。舉個例子:若是散列表T中的槽T[i]、T[i+1]、T[i+2]個元素已經存儲了某些關鍵字,則下一次哈希地址(即經過散列函數獲得的地址)爲i、i+一、i+二、i+3的關鍵字都會企圖填入槽T[i+3]中,就產生多個關鍵字爭奪同一個槽的現象,將會極大地影響查找效率。

二次探查

二次探查採用以下的散列函數:h(k,i)=(h'(k)+c1i+c2i2) mod m, i=0,1,...,m-1,c1、c2爲正的輔助常數。

初始的探查位置爲T[h'(k)],後續的探查位置要加上一個偏移量,該偏移已二次的方式依賴於探查序號i。此外,若是兩個兩個關鍵字的初始探查位置相同,那麼他們的探查序列也是相同的,這樣會致使一種輕度的羣集,稱爲二次羣集。並且此方法也不可以探查到全部的存儲單元。

雙重散列

雙重散列是用於開放尋址法中的最好的方法之一,由於它所產生的排列具備隨機選擇排列的許多特性。雙重散列具備以下的散列函數:h(k,i)=(h1(k)+i*h2(k)) mod m, i=0,1,...,m-1,h1、h2均爲輔助散列函數。

初始的探查位置爲T[h'(k)],後續的探查位置是前一個探查加上偏移量h2(k)後模m。此種探查序列以兩種不一樣的方式依賴於關鍵字k。雙重探查要求h2(k)與表的大小m互素(互素,就是互爲質數,兩個數之間除了1以外沒有更多的公約數)。一種簡便的方法是:m取2的冪,h2(k)只產生奇數;或者m爲素數,h2(k)老是返回比m小的正整數。

舉個例子:取m爲素數,並取h1(k)=k mod m,h2(k)=1 + (k mod m'),其中m'略小於m(如m-1)。例如,若是k=123456,m=701,m'=700,則有h1(k)=80,h2=257,可知咱們的第一個探查位置爲80,而後檢查每第257個槽,直到找到該關鍵字,或者遍歷了全部的槽。

開放尋址法分析

使用開放尋址表,每一個槽中最多隻有一個元素,因此n<=m,也就意味着α<=1。對於開放尋址表來講,有三個定理和推論,由於證實起來比較麻煩,因此直接上結果,有興趣的能夠自行了解。

  1. 給定一個裝載因子爲α=n/m < 1的開放尋址散列表,並假設是均勻散列的,則對於一次不成功的查找,其指望的探查次數至多爲1/(1-α)
  2. 假設採用的是均勻散列,平均狀況下,向一個裝載因子爲α的開放尋址散列表中插入一個元素至多須要作1/(1-α)此探查
  3. 假設採用均勻散列,且表中的每一個關鍵字被查找的可能性是相同的,一次成功查找中的探查數至多爲1/α*ln(1/(1-α))

總結:散列表越滿,即α越大,所需執行探查的查找/插入指望數越小。

徹底散列

徹底散列即經過一個不含衝突的散列表解決衝突,在最壞狀況下也只需O(1)次訪問。以下圖:

二叉搜索樹

二叉搜索樹的特色:假設x是二叉搜索樹的一個結點,若是y是x左子樹中的一個結點,那麼y.key<=x.key。若是y是x右子樹中的一個結點,那麼y.key>=x.key。即相對根節點來講,左子樹存放小值,右子樹存放大值。那麼中序遍歷二叉樹(左子樹->根節點->右子樹)就是一個排序的過程。

二叉搜索樹的查詢/插入/刪除的運行時間爲O(h),h爲樹的高度。一棵有n個不一樣關鍵字隨機構建的二叉搜索樹的指望高度爲O(logn)。這個不難理解,大學課堂上也講到過,有興趣或遺忘的能夠自行查閱。

二叉搜索樹在屢次插入新節點後容易致使不平衡。以下圖,原樹爲四、五、6,在分別插入四、三、二、1後發現這棵樹變的極不平衡,已經變成了線性結構。爲了解決這種不平衡,就有了下一節所提到的紅黑樹。

紅黑樹

紅黑樹是一棵二叉搜索樹,它在每一個結點上增長了一個存儲位來表示結點的顏色,能夠是RED或者BLACK。紅黑樹是知足如下紅黑性質的二叉搜索樹:

  1. 每一個結點是紅色或者黑色
  2. 根結點是黑色
  3. 每一個葉節點(NIL)是黑色
  4. 若是一個結點是紅色的,則它的兩個子結點都是黑色的
  5. 對每一個結點,從根結點到其全部後代葉結點的簡單路徑上,均包含相同數目的黑色結點

紅黑樹由於這些特性的限制,確保沒有一條路徑會比其餘路徑長出2倍,於是是近似平衡的。

紅黑樹的查找性能很是好,也是基於其上述特性的緣由。使其在最壞狀況下的查找時間也能有O(logn)。紅黑樹的插入和刪除操做有可能會打破以上原則,因此須要進行自平衡。關於插入和刪除的過程,推薦看下這篇文章。這裏貼上其餘文章主要是由於我也講不清=_=,流下了又菜又沒有技術的淚水。

固然,學習數據結構不只僅是要學會其基礎知識,更多地是爲了擴展數據結構以使其知足咱們的需求。例如,在紅黑樹的基礎上能夠擴展出順序統計樹、區間樹,有興趣的能夠自行了解。更多的,咱們是學習數據結構是爲了在實際應用用一個合理的模型去描述一個具體的問題,得到一種較效率的解決方案。本章內容內容到此結束,關於圖的內容後續再說,由於我也還沒看到,下一章預告:高級設計和分析技術。

相關文章
相關標籤/搜索