Python面試——數據結構(三)

查找

順序查找、折半查找、索引查找、分塊查找是靜態查找,動態查找有二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希表查找

靜態查找表

靜態查找表(Static Search Table):只作查找操作的查找表。  A:查詢某個“特定”數據元素是否在查找表中;  B:檢索某個“特定”數據元素和各種屬性。  動態查找表(Dynamic Search Table):在查找過程同時插入查找表中不存在的數據元素,或者從查找表中刪除已經存在的某個數據元素。  A:查找是插入數據元素;  B:查找時刪除數據元素。 

順序表的順序查找:應用範圍:順序表或線性鏈表表示的表,表內元素之間無序。查找過程:從表的一端開始逐個進行記錄的關鍵字和給定值的比較。

順序有序表的二分查找。平均查找時間(n+1)/n log2(n+1),時間複雜度:x=logn,底數是2,

分塊查找:將表分成幾塊,塊內無序,塊間有序,即前一塊中的最大值小於後一塊中的最小值。並且有一張索引表,每一項存放每一塊的最大值和指向該塊第一個元素的指針。索引表有序,塊內無序。所以,塊間查找用二分查找,塊內用順序查找,效率介於順序和二分之間;先確定待查記錄所在塊,再在塊內查找。因此跟表中元素個數和塊中元素個數都有關。

  1. 用數組存放待查記錄,
  2. 建立索引表,由每塊中最大(小)的關鍵字及所屬塊位置的信息組成。
  3. 當索引表較大時,可以採用二分查找
  4. 在數據量極大時,索引可能很多,可考慮建立索引表的索引,即二級索引,原則上索引不超過三級

分塊查找平均查找長度:ASLbs = Lb + Lw。其中,Lb是查找索引表確定所在塊的平均查找長度, Lw是在塊中查找元素的平均查找長度。在n一定時,可以通過選擇s使ASL儘可能小。當s=sqrt(n)時,ASL最小。

  1. 時間:順序查找最差,二分最好,分塊介於兩者之間
  2. 空間:分塊最大,需要增加索引數據的空間
  3. 順序查找對錶沒有特殊要求
  4. 分塊時數據塊之間在物理上可不連續。所以可以達到插入、刪除數據只涉及對應的塊;另外,增加了索引的維護。
  5. 二分查找要求表有序,所以若表的元素的插入與刪除很頻繁,維持表有序的工作量極大。
  6. 在表不大時,一般直接使用順序查找。

動態查找

常見的平衡二叉樹:

  1. 紅黑樹是平衡二叉樹,也就是左右子樹是平衡的,高度大概相等。這種情況等價於一塊完全二叉樹的高度,查找的時間複雜度是樹的高度,爲logn,插入操作的平均時間複雜度爲O(logn),最壞時間複雜度爲O(logn) 紅黑樹
    • 節點是紅色或黑色。
    • 根是黑色。
    • 所有葉子都是黑色(葉子是NIL節點)。
    • 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
    • 從任一節點到其每個葉子的所有簡單路徑 都包含相同數目的黑色節點。
  2. avl樹也是自平衡二叉樹;紅黑樹和AVL樹查找、插入、刪除的時間複雜度相同;包含n個內部結點的紅黑樹的高度是o(logn); TreeMap 是一個紅黑樹的實現,能保證插入的值保證排序
  3. STL和linux多使用紅黑樹作爲平衡樹的實現:
    1. 如果插入一個node引起了樹的不平衡,AVL和RB-Tree都是最多隻需要2次旋轉操作,即兩者都是O(1);但是在刪除node引起樹的不平衡時,最壞情況下,AVL需要維護從被刪node到root這條路徑上所有node的平衡性,因此需要旋轉的量級O(logN),而RB-Tree最多隻需3次旋轉,只需要O(1)的複雜度。
    2. 其次,AVL的結構相較RB-Tree來說更爲平衡,在插入和刪除node更容易引起Tree的unbalance,因此在大量數據需要插入或者刪除時,AVL需要rebalance的頻率會更高。因此,RB-Tree在需要大量插入和刪除node的場景下,效率更高。自然,由於AVL高度平衡,因此AVL的search效率更高。
    3. map的實現只是折衷了兩者在search、insert以及delete下的效率。總體來說,RB-tree的統計性能是高於AVL的。

查找總結

  1. 既希望較快的查找又便於線性表動態變化的查找方法是哈希法查找。二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希法查找是動態查找。分塊、順序、折半、索引順序查找均爲靜態。分塊法應該是將整個線性表分成若干塊進行保存,若動態變化則可以添加在表的尾部(非順序結構),時間複雜度是O(1),查找複雜度爲O(n);若每個表內部爲順序結構,則可用二分法將查找時間複雜度降至O(logn),但同時動態變化複雜度則變成O(n);順序法是挨個查找,這種方法最容易實現,不過查找時間複雜度都是O(n),動態變化時可將保存值放入線性表尾部,則時間複雜度爲O(1);二分法是基於順序表的一種查找方式,時間複雜度爲O(logn);通過哈希函數將值轉化成存放該值的目標地址,O(1)
  2. 二叉樹的平均查找長度爲O(log2n)——O(n).二叉排序樹的查找效率與二叉樹的高度有關,高度越低,查找效率越高。二叉樹的查找成功的平均查找長度ASL不超過二叉樹的高度。二叉樹的高度與二叉樹的形態有關,n個節點的完全二叉樹高度最小,高度爲[log2n]+1,n個節點的單隻二叉樹的高度最大,高度爲n,此時查找成功的ASL爲最大(n+1)/2,因此二叉樹的高度範圍爲[log2n]+1——n.
  3. 鏈式存儲不能隨機訪問,必須是順序存儲

哈希表

  1. 在記錄的存儲地址和它的關鍵字之間建立一個確定的對應關係;這樣不經過比較,一次存取就能得到元素
  2. 哈希函數——在記錄的關鍵字與記錄的存儲位置之間建立的一種對應關係。是從關鍵字空間到存儲位置空間的一種映象。
  3. 哈希表——應用哈希函數,由記錄的關鍵字確定記錄在表中的位置信息,並將記錄根據此信息放入表中,這樣構成的表叫哈希表。
  4. Hash查找適合於關鍵字可能出現的值的集合遠遠大於實際關鍵字集合的情形。
  5. 更適合查找,不適合頻繁更新
  6. Hash表等查找複雜依賴於Hash值算法的有效性,在最好的情況下,hash表查找複雜度爲O(1)。只有無衝突的hash_table複雜度纔是O(1)。一般是O(c),c爲哈希關鍵字衝突時查找的平均長度。插入,刪除,查找都是O(1)。平均查找長度不隨表中結點數目的增加而增加,而是隨負載因子的增大而增大
  7. 由於衝突的產生,使得哈希表的查找過程仍然是一個給定值與關鍵字比較的過程。

根據抽屜原理,衝突是不可能完全避免的,所以,選擇好的散列函數和衝突處理方法:

  1. 構造一個性能好,衝突少的Hash函數
  2. 如何解決衝突

常用的哈希函數

  1. 直接定址法。僅適合於:地址集合的大小 == 關鍵字集合的大小
  2. 數字分析法。對關鍵字進行分析,取關鍵字的若干位或其組合作哈希地址。僅適合於:能預先估計出全體關鍵字的每一位上各種數字出現的頻度。
  3. 平方取中法。以關鍵字的平方值的中間幾位作爲存儲地址。
  4. 摺疊法。將關鍵字分割成位數相同的幾部分,然後取這幾部分的疊加和(捨去進位)做哈希地址。移位疊加/間界疊加。適合於: 關鍵字的數字位數特別多,且每一位上數字分佈大致均勻情況。
  5. 除留餘數法。取關鍵字被某個不大於哈希表表長m的數p除後所得餘數作哈希地址,即H(key)=key%p,p<=m。
  6. 隨機數法。取關鍵字的僞隨機函數值作哈希地址,即H(key)=random(key),適於關鍵字長度不等的情況。

衝突解決

  1. 開放定址法。當衝突發生時,形成一個探查序列;沿此序列逐個地址探查,直到找到一個空位置(開放的地址),將發生衝突的記錄放到該地址中。即Hi=(H(key)+di) % m,i=1,2,……k(k<=m-1),H(key)哈希函數,m哈希表長,di增量序列。缺點:刪除:只能作標記,不能真正刪除;溢出;載因子過大、解決衝突的算法選擇不好會發生聚集問題。要求裝填因子α較小,故當結點規模較大時會浪費很多空間。
    • 線性探測再散列:di=1,2,3,...,m-1
    • 二次探測再散列:di=12,-12,22,-22,...,±k2(k<=m/2)
    • 僞隨機探測再散列: di爲僞隨機數序列
  2. 鏈地址法:將所有關鍵字爲同義詞的記錄存儲在一個單鏈表中,並用一維數組存放頭指針。拉鍊法中可取α≥1,且結點較大時,拉鍊法中增加的指針域可忽略不計,因此節省空間。一旦發生衝突,在當前位置給單鏈表增加結點就行。
  3. 其他方法:再哈希法、建立公共溢出區
  4. 在用拉鍊法構造的散列表中,刪除結點的操作易於實現。拉鍊法的缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間。由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況。拉鍊法解決衝突時,需要使用指針,指示下一個元素的存儲位置
  5. 開哈希表--鏈式地址法;閉哈希表--開放地址法.開哈希和閉哈希主要的區別在於,隨着哈希表的密集度提高,使用閉哈希時,不僅會與相同哈希值的元素髮生衝突,還容易與不同哈希值的元素髮生衝突;而開哈希則不受哈希表疏密與否的影響,始終只會與相同哈希值的元素衝突而已。所以在密集度變大的哈希表中查找時,顯然開哈希的平均搜索長度不會增長。
  6. 設有n個關鍵字具有相同的Hash函數值,則用線性探測法把這n個關鍵字映射到Hash表中需要做n*(n-1)/2次線性探測。如果使用二次探測再散列法將這n個關鍵字存入哈希表,至少要進行n*(n+1)/2次探測

Hash查找效率:裝填因子=表中記錄數/表容量

有B+Tree/Hash_Map/STL Map三種數據結構。對於內存中數據,查找性能較好的數據結構是Hash_Map,對於磁盤中數據,查找性能較好的數據結構是B+Tree。Hash操作能根據散列值直接定位數據的存儲地址,設計良好的hash表能在常數級時間下找到需要的數據,但是更適合於內存中的查找。B+樹是一種是一種樹狀的數據結構,適合做索引,對磁盤數據來說,索引查找是比較高效的。STL_Map的內部實現是一顆紅黑樹,但是隻是一顆在內存中建立二叉樹樹,不能用於磁盤操作,而其內存查找性能也比不上Hash查找。

內部排序

  1. 內部排序:全部數據可同時放入內存進行的排序。
  2. 外部排序:文件中數據太多,無法全部調入內存進行的排序。

插入類:

  1. 直接插入排序。最壞情況是數據遞減序,數據比較和移動量最大,達到O(n2),最好是數據是遞增序,比較和移動最少爲O(n)。趟數是固定的n-1,即使有序,也要依次從第二個元素開始。排序趟數不等於時間複雜度。
  2. 折半插入排序 。由於插入第i個元素到r[1]到r[i-1]之間時,前i個數據是有序的,所以可以用折半查找確定插入位置,然後插入。
  3. 希爾排序。縮小增量排序。5-3-1。在實際應用中,步長的選取可簡化爲開始爲表長n的一半(n/2),以後每次減半,最後爲1。插入的改進,最後一趟已基本有序,比較次數和移動次數相比直接插入最後一趟更少

交換類:

  1. 冒泡排序。O(n2)通常認爲冒泡是比較差的,可以加些改進,比如在一趟中無數據的交換,則結束等措施。
    • 在數據已基本有序時,冒泡是一個較好的方法
    • 在數據量較少時(15個左右)可以用冒泡
  2. 快速排序。
    • 時間複雜度。最好情況:每次支點總在中間,O(nlog2n),平均O(nlog2n)。最壞,數據已是遞增或遞減,O(n2)。pivotkey的選擇越靠近中央,即左右兩個子序列長度越接近,排序速度越快。越無序越快。
    • 空間複雜度。需棧空間以實現遞歸,最壞情況:S(n)=O(n);一般情況:S(n)=O(log2n)
    • 在序列已是有序的情況下,時間複雜度最高。原因:支點選擇不當。改進:隨機選取支點或最左、最右、中間三個元素中的值處於中間的作爲支點,通常可以避免最壞情況。所以,快速排序在表已基本有序的情況下不合適。
    • 在序列長度已較短時,採用直接插入排序、起泡排序等排序方法。序列的個數通常取10左右。

選擇類排序:

  1. 簡單選擇排序。O(n2)。總比較次數n(n-1)/2。
  2. 堆排序。建堆 O(n),篩選排序O(nlogn)。找出若干個數中最大/最小的前K個數,用堆排序是最好。小根堆中最大的數一定是放在葉子節點上,堆本身是個完全二叉樹,完全二叉樹的葉子節點的位置大於[n/2]。時間複雜度不會因爲待排序序列的有序程度而改變,但是待排序序列的有序程度會影響比較次數。
  3. 歸併排序。時間:與表長成正比,若一個表表長是m,另一個是n,則時間是O(m+n)。單獨一個數組歸併,時間:O(nlogn),空間:O(n),比較次數介於(nlogn)/2和(nlogn)-n+1,賦值操作的次數是(2nlogn)。歸併排序算法比較佔用內存,但卻是效率高且穩定的排序算法。在外排序中使用。歸併的趟數是logn。
  4. 基數排序。在一般情況下,每個結點有 d 位關鍵字,必須執行 t = d次分配和收集操作。分配的代價:O(n);收集的代價:O(rd) (rd是基數);總的代價爲:O( d ×(n + rd))。適用於以數字和字符串爲關鍵字的情況。
  5. 枚舉排序,通常也被叫做秩排序,比較計數排序。對每一個要排序的元素,統計小於它的所有元素的個數,從而得到該元素在整個序列中的位置,時間複雜度爲O(n2)

比較法分類的下界:O(nlogn)

排序算法的一些特點:

  1. 堆排序、冒泡排序、快速排序在每趟排序過程中,都會有一個元素被放置在其最終的位置上。
  2. 有字符序列 {Q,H,C,Y,P,A,M,S,R,D,F,X} ,新序列{F,H,C,D,P,A,M,Q,R,S,Y,X},是快速排序算法一趟掃描的結果。(拿Q作爲分割點,快速排序一輪。二路歸併,第一趟排序,得到 n / 2 個長度爲 2 的各自有序的子序列,第二趟排序,得到 n / 4 個長度爲 4 的各自有序的子序列H Q C Y A P M S D R F X。如果是快速排序的話,第一個元素t將會被放到一個最準確的位置,t前的數均小於t,後面的數均大於t。希爾排序每個小分組內將會是有序的。堆排序,把它構成一顆二叉樹的時候,該堆要麼就是大根堆,要麼就是小根堆,第一趟Y排在最後;冒泡,那麼肯定會有數據下沉的動作,第一趟有A在第一位。)
  3. 在文件"局部有序"或文件長度較小的情況下,最佳內部排序的方法是直接插入排序。(歸併排序要求待排序列已經部分有序,而部分有序的含義是待排序列由若干有序的子序列組成,即每個子序列必須有序,並且其時間複雜度爲O(nlog2n);直接插入排序在待排序列基本有序時,每趟的比較次數大爲降低,即n-1趟比較的時間複雜度由O(n^2)降至O(n)。在待排序的元素序列基本有序或者每個元素距其最終位置不遠也可用插入排序,效率最高的排序方法是插入排序
  4. 排序趟數與序列的原始狀態有關的排序方法是優化冒泡和快速排序法。(插入排序和選擇排序不管序列的原始狀態是什麼都要執行n-1趟,優化冒泡和快排不一定。仔細理解排序的次數比較次數的區別)
  5. 不穩定的排序方法:快排,堆排,希爾,選擇
  6. 要與關鍵字的初始排列次序無關,那麼就是最好、最壞、一般的情況下排序時間複雜度不變, 總共有堆排序,歸併排序,選擇排序,基數排序
  7. 快速排序、Shell 排序、歸併排序、直接插入排序的關鍵碼比較次數與記錄的初始排列有關。折半插入排序、選擇排序無關。(直接插入排序在完全有序的情況下每個元素只需要與他左邊的元素比較一次就可以確定他最終的位置;折半插入排序,比較次數是固定的,與初始排序無關;快速排序,初始排序不影響每次劃分時的比較次數,都要比較n次,但是初始排序會影響劃分次數,所以會影響總的比較次數,但快排平均比較次數最小;歸併排序在歸併的時候,如果右路最小值比左路最大值還大,那麼只需要比較n次,如果右路每個元素分別比左路對應位置的元素大,那麼需要比較2*n-1次,所以與初始排序有關)
  8. 精儉排序,即一對數字不進行兩次和兩次以上的比較,插入和歸併是“精儉排序”。插入排序,前面是有序的,後面的每一個元素與前面有序的元素比較,比較過的就是有序的了,不會再比較一次。歸併每次合併後,內部都是有序的,內部的元素之間不用再比較。選擇排序,每次在後面的元素中找到最小的,找最小元素的過程是在沒有排好序的那部分進行,所有肯定會比較多次。堆排序也需比較多次。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章