樹(續)
二叉樹
二叉排序樹
二叉排序樹,又叫二叉查找樹,它或者是一棵空樹;或者是具有以下性質的二叉樹:
- 若它的左子樹不空,則左子樹上所有節點的值均小于它的根節點的值;
- 若它的右子樹不空,則右子樹上所有節點的值均大于它的根節點的值;
- 它的左右子樹也分別為二叉排序樹
- 二叉排序樹的建立
集合{62, 88, 58, 47, 35, 73, 51, 99, 37, 93}中的元素放入到我們的二叉排序樹中去存儲,如果對我們創建好的二叉排序樹進行中序搜索的話,輸出的結果就是上面集合的有序序列。下方就是我們二叉排序樹從無到有的完整創建過程。
- 在初始化狀態下我們二叉排序樹的根節點為空,我們依次將集合中的元素通過搜索插入到二叉排序樹中合適的位置。
- 首先在二叉排序中進行搜索62的位置,樹為空,所以將62存入到二叉排序樹的根節點中,及root=(62)。
- 從集合中取出88,然后查找我們的二叉排序樹,發現88大于我們的根節點62,所以將88插入到62節點的右子樹中,即(62)->rightChild=(88)。
- 從集合中取出58,然后從根節點開始查找我們現有的二叉排序樹,發現55<62,將55作為62的左結點,即(62)->leftChild=(55)。
- 從集合中取出47,然后對二叉排序樹進行搜索,發現47<55, 所以(55)->leftChild=(47)。
- 從集合中取出35,再次對二叉排序樹進行搜索,發現35又小于47,所以(47)->leftChild=(35)。
- 從集合中取出73,再次對二叉排序樹進行搜索,發現62<73<88, 所以有(88)->leftChild=(73)。
以此類推,要做的事情就是不斷從集合中取值,然后對二叉排序樹進行查找,找到合適的插入點,然后將相應的節點進行插入,具體步驟就不做過多贅述了。
- 二叉排序樹節點的刪除
冒泡手法
二叉平衡樹
二叉排序樹的結點刪除:
x為葉子結點,則直接刪除
x只有左子樹xL或只有右子樹xR ,則令xL或xR直接成為雙親結點f的子樹;
x即有左子樹xL也有右子樹xR,在xL中選值最大的代替x,該數據按二叉排序樹的性質應在最右邊。
平衡二叉樹:每個結點的平衡因子都為 1、-1、0 的二叉排序樹?;蛘哒f每個結點的左右子樹的高度最多差1的二叉排序樹。平衡二叉樹的平衡:
左調整(新結點插入在左子樹上的調整):
LL(插入在結點左子樹的左子樹上):旋轉前后高度都為h+1
LR(新插入結點在左子樹的右子樹上):旋轉前后高度仍為h+1
右調整(新結點插入在右子樹上進行的調整):
RR(插入在的右子樹的右子樹上):處理方法和 LL對稱
RL(插入在的右子樹的左子樹上):處理方法和 LR對稱平衡樹建立方法:
按二叉排序樹插入結點
如引起結點平衡因子變為|2|,則確定旋轉點,該點是離根最遠(或最接近于葉子的點)
確定平衡類型后進行平衡處理,平衡后以平衡點為根的子樹高不變
最小二叉平衡樹的節點的公式如下 F(n)=F(n-1)+F(n-2)+1 這個類似于一個遞歸的數列,可以參考Fibonacci數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。eg:
鏡像二叉樹
有限單詞拼寫錯誤檢查
二叉樹中和為某一值的路徑
二叉搜索樹第k個結點
按之字順序打印二叉樹
圖(續)
圖的存儲形式
- 鄰接矩陣和加權鄰接矩陣
無權有向圖:出度: i行之和;入度: j列之和。
無權無向圖:i結點的度: i行或i列之和。
加權鄰接矩陣:相連為w,不相連為∞ - 鄰接表
用頂點數組表、邊(弧)表表示該有向圖或無向圖
頂點數組表:用數組存放所有的頂點。數組大小為圖頂點數n
邊表(邊結點表):每條邊用一個結點進行表示。同一個結點的所有的邊形成它的邊結點單鏈表。
n個頂點的無向圖的鄰接表最多有n(n-1)個邊表結點。有n個頂點的無向圖最多有n(n-1)/2條邊,此時為完全無向圖,而在鄰接表中每條邊存儲兩次,所以有n(n-1)個結點
圖的遍歷
深度優先搜索利用棧,廣度優先搜索利用隊列
環
環的檢測:黑白灰算法,死鎖檢測(拓撲排序)
- 在有向圖中選一個沒有前驅的頂點且輸出之
- 從圖中刪除該頂點和所有以它為尾的弧
- 重復上述兩步,直至全部頂點均已輸出;或者當圖中不存在無前驅的頂點為止(此時說明圖中有環)
最短路徑
貪心算法:Dijkstra算法
最小生成樹
貪心算法:Prim算法、Kruskal算法
AOE網:
帶權的有向無環圖,其中頂點表示事件,弧表示活動,權表示活動持續時間。在工程上常用來表示工程進度計劃。
- 事件的最早發生時間(ve(j)):從源點到j結點的最長的路徑。意味著事件最早能夠發生的時間。
- 事件的最遲發生時間(vl(j)):不影響工程的如期完工,事件j必須發生的時間。
- 活動ai由弧<j,k>表示,持續時間記為 dut<j,k>,則有:
活動的最早開始時間:e(i)=ve(j)
活動的最遲開始時間:l(i)=vl(k) - dut(<j , k >)
活動余量:l(i)-e(i)的差 - 關鍵活動:活動余量為0的活動
- 關鍵路徑:從源點到匯點的最長的一條路徑,或者全部由關鍵活動構成的路徑。關鍵活動一定位于關鍵路徑上。
- 關鍵活動組成了關鍵路徑,關鍵路徑是圖中的最長路徑,關鍵路徑長度代表整個工期的最短完成時間,關鍵活動延期完成,必將導致關鍵路徑長度增加,即整個工期的最短完成時間增加。關鍵路徑并不唯一,當有多條關鍵路徑存在時,其中一條關鍵路徑上的關鍵活動時間縮短,只能導致本條關鍵路徑變成非關鍵路徑,而無法縮短整個工期,因為其他關鍵路徑沒有變化。任何一條關鍵路徑上的關鍵活動變長了,都會使這條關鍵路徑變成更長的關鍵路徑,并且導致其他關鍵路徑變成非關鍵路徑(如果關鍵路徑不唯一)。關鍵活動不按期完成就會影響整個工程的完成時間。所有的關鍵活動提前完成,那么整個工程才會提前完成。關鍵路徑也不能任意縮短,一旦縮短到一定程度,該關鍵活動可能變成非關鍵活動了。
查找
順序查找、折半查找、索引查找、分塊查找 vs 二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希表查找
-
tips:
時間:順序查找最差,二分最好,分塊介于兩者之間
空間:分塊最大,需要增加索引數據的空間
順序查找對表沒有特殊要求
分塊時數據塊之間在物理上可不連續。所以可以達到插入、刪除數據只涉及對應的塊;另外,增加了索引的維護。
二分查找要求表有序,所以若表的元素的插入與刪除很頻繁,維持表有序的工作量極大。
在表不大時,一般直接使用順序查找。
既希望較快的查找又便于線性表動態變化的查找方法是哈希法查找。
二叉排序樹查找,最優二叉樹查找,鍵樹查找,哈希法查找是動態查找。分塊、順序、折半、索引順序查找均為靜態。分塊法應該是將整個線性表分成若干塊進行保存,若動態變化則可以添加在表的尾部(非順序結構),時間復雜度是O(1),查找復雜度為O(n);若每個表內部為順序結構,則可用二分法將查找時間復雜度降至O(logn),但同時動態變化復雜度則變成O(n);順序法是挨個查找,這種方法最容易實現,不過查找時間復雜度都是O(n),動態變化時可將保存值放入線性表尾部,則時間復雜度為O(1);二分法是基于順序表的一種查找方式,時間復雜度為O(logn);通過哈希函數將值轉化成存放該值的目標地址,O(1)
二叉樹的平均查找長度為O(log2n)——O(n).二叉排序樹的查找效率與二叉樹的高度有關,高度越低,查找效率越高。二叉樹的查找成功的平均查找長度ASL不超過二叉樹的高度。二叉樹的高度與二叉樹的形態有關,n個節點的完全二叉樹高度最小,高度為[log2n]+1,n個節點的單只二叉樹的高度最大,高度為n,此時查找成功的ASL為最大(n+1)/2,因此二叉樹的高度范圍為[log2n]+1——n.
鏈式存儲不能隨機訪問,必須是順序存儲
一種二叉平衡樹
哈希表
在記錄的存儲地址和它的關鍵字之間建立一個確定的對應關系;這樣不經過比較,一次存取就能得到元素。
哈希函數——在記錄的關鍵字與記錄的存儲位置之間建立的一種對應關系。是從關鍵字空間到存儲位置空間的一種映象。
哈希表——應用哈希函數,由記錄的關鍵字確定記錄在表中的位置信息,并將記錄根據此信息放入表中,這樣構成的表叫哈希表。
Hash查找適合于關鍵字可能出現的值的集合遠遠大于實際關鍵字集合的情形。
更適合查找,不適合頻繁更新
Hash表等查找復雜依賴于Hash值算法的有效性,在最好的情況下,hash表查找復雜度為O(1)。只有無沖突的hash_table復雜度才是O(1)。一般是O(c),c為哈希關鍵字沖突時查找的平均長度。插入,刪除,查找都是O(1)。平均查找長度不隨表中結點數目的增加而增加,而是隨負載因子的增大而增大
由于沖突的產生,使得哈希表的查找過程仍然是一個給定值與關鍵字比較的過程。
根據抽屜原理,沖突是不可能完全避免的,所以,選擇好的散列函數和沖突處理方法:
構造一個性能好,沖突少的Hash函數
如何解決沖突
常用的哈希函數
直接定址法。僅適合于:地址集合的大小 == 關鍵字集合的大小
數字分析法。對關鍵字進行分析,取關鍵字的若干位或其組合作哈希地址。僅適合于:能預先估計出全體關鍵字的每一位上各種數字出現的頻度。
平方取中法。以關鍵字的平方值的中間幾位作為存儲地址。
折疊法。將關鍵字分割成位數相同的幾部分,然后取這幾部分的疊加和(舍去進位)做哈希地址。移位疊加/間界疊加。適合于: 關鍵字的數字位數特別多,且每一位上數字分布大致均勻情況。
除留余數法。取關鍵字被某個不大于哈希表表長m的數p除后所得余數作哈希地址,即H(key)=key%p,p<=m。
隨機數法。取關鍵字的偽隨機函數值作哈希地址,即H(key)=random(key),適于關鍵字長度不等的情況。
沖突解決
開放定址法。當沖突發生時,形成一個探查序列;沿此序列逐個地址探查,直到找到一個空位置(開放的地址),將發生沖突的記錄放到該地址中。即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為偽隨機數序列
鏈地址法:將所有關鍵字為同義詞的記錄存儲在一個單鏈表中,并用一維數組存放頭指針。拉鏈法中可取α≥1,且結點較大時,拉鏈法中增加的指針域可忽略不計,因此節省空間。一旦發生沖突,在當前位置給單鏈表增加結點就行。
其他方法:再哈希法、建立公共溢出區
在用拉鏈法構造的散列表中,刪除結點的操作易于實現。拉鏈法的缺點是:指針需要額外的空間,故當結點規模較小時,開放定址法較為節省空間。由于拉鏈法中各鏈表上的結點空間是動態申請的,故它更適合于造表前無法確定表長的情況。拉鏈法解決沖突時,需要使用指針,指示下一個元素的存儲位置
開哈希表--鏈式地址法;閉哈希表--開放地址法.開哈希和閉哈希主要的區別在于,隨著哈希表的密集度提高,使用閉哈希時,不僅會與相同哈希值的元素發生沖突,還容易與不同哈希值的元素發生沖突;而開哈希則不受哈希表疏密與否的影響,始終只會與相同哈希值的元素沖突而已。所以在密集度變大的哈希表中查找時,顯然開哈希的平均搜索長度不會增長。
設有n個關鍵字具有相同的Hash函數值,則用線性探測法把這n個關鍵字映射到Hash表中需要做n(n-1)/2次線性探測。如果使用二次探測再散列法將這n個關鍵字存入哈希表,至少要進行n(n+1)/2次探測
Hash查找效率:裝填因子=表中記錄數/表容量
排序
內部排序:全部數據可同時放入內存進行的排序。
外部排序:文件中數據太多,無法全部調入內存進行的排序。
內部排序
- 插入類:
- 直接插入排序。最壞情況是數據遞減序,數據比較和移動量最大,達到O(n2),最好是數據是遞增序,比較和移動最少為O(n)。趟數是固定的n-1,即使有序,也要依次從第二個元素開始。排序趟數不等于時間復雜度。
- 折半插入排序 。由于插入第i個元素到r[1]到r[i-1]之間時,前i個數據是有序的,所以可以用折半查找確定插入位置,然后插入。
- 希爾排序。縮小增量排序。5-3-1。在實際應用中,步長的選取可簡化為開始為表長n的一半(n/2),以后每次減半,最后為1。插入的改進,最后一趟已基本有序,比較次數和移動次數相比直接插入最后一趟更少
- 交換類:
- 冒泡排序。O(n2)通常認為冒泡是比較差的,可以加些改進,比如在一趟中無數據的交換,則結束等措施。
在數據已基本有序時,冒泡是一個較好的方法
在數據量較少時(15個左右)可以用冒泡 - 快速排序。
時間復雜度。最好情況:每次支點總在中間,O(nlog2n),平均O(nlog2n)。最壞,數據已是遞增或遞減,O(n2)。pivotkey的選擇越靠近中央,即左右兩個子序列長度越接近,排序速度越快。越無序越快。
空間復雜度。需??臻g以實現遞歸,最壞情況:S(n)=O(n);一般情況:S(n)=O(log2n)
在序列已是有序的情況下,時間復雜度最高。原因:支點選擇不當。改進:隨機選取支點或最左、最右、中間三個元素中的值處于中間的作為支點,通常可以避免最壞情況。所以,快速排序在表已基本有序的情況下不合適。
在序列長度已較短時,采用直接插入排序、起泡排序等排序方法。序列的個數通常取10左右。
- 選擇類排序:
- 簡單選擇排序。O(n2)??偙容^次數n(n-1)/2。(類似冒泡排序)
- 堆排序。建堆 O(n),篩選排序O(nlogn)。找出若干個數中最大/最小的前K個數,用堆排序是最好。小根堆中最大的數一定是放在葉子節點上,堆本身是個完全二叉樹,完全二叉樹的葉子節點的位置大于[n/2]。時間復雜度不會因為待排序序列的有序程度而改變,但是待排序序列的有序程度會影響比較次數。
- 歸并排序。時間:與表長成正比,若一個表表長是m,另一個是n,則時間是O(m+n)。單獨一個數組歸并,時間:O(nlogn),空間:O(n),比較次數介于(nlogn)/2和(nlogn)-n+1,賦值操作的次數是(2nlogn)。歸并排序算法比較占用內存,但卻是效率高且穩定的排序算法。在外排序中使用。歸并的趟數是logn。
- 基數排序。在一般情況下,每個結點有 d 位關鍵字,必須執行 t = d次分配和收集操作。分配的代價:O(n);收集的代價:O(rd) (rd是基數);總的代價為:O( d ×(n + rd))。適用于以數字和字符串為關鍵字的情況。
- 枚舉排序,通常也被叫做秩排序,比較計數排序。對每一個要排序的元素,統計小于它的所有元素的個數,從而得到該元素在整個序列中的位置,時間復雜度為O(n2)
- 排序算法的一些特點:
- 堆排序、冒泡排序、快速排序在每趟排序過程中,都會有一個元素被放置在其最終的位置上。
- 有字符序列 {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在第一位。)
- 在文件"局部有序"或文件長度較小的情況下,最佳內部排序的方法是直接插入排序。(歸并排序要求待排序列已經部分有序,而部分有序的含義是待排序列由若干有序的子序列組成,即每個子序列必須有序,并且其時間復雜度為O(nlog2n);直接插入排序在待排序列基本有序時,每趟的比較次數大為降低,即n-1趟比較的時間復雜度由O(n^2)降至O(n)。在待排序的元素序列基本有序或者每個元素距其最終位置不遠也可用插入排序,效率最高的排序方法是插入排序)
- 排序趟數與序列的原始狀態有關的排序方法是優化冒泡和快速排序法。(插入排序和選擇排序不管序列的原始狀態是什么都要執行n-1趟,優化冒泡和快排不一定。仔細理解排序的次數和比較次數的區別)
- 不穩定的排序方法:快排,堆排,希爾,選擇
- 要與關鍵字的初始排列次序無關,那么就是最好、最壞、一般的情況下排序時間復雜度不變, 總共有堆排序,歸并排序,選擇排序,基數排序.
快速排序、Shell 排序、歸并排序、直接插入排序的關鍵碼比較次數與記錄的初始排列有關。折半插入排序、選擇排序無關。(直接插入排序在完全有序的情況下每個元素只需要與他左邊的元素比較一次就可以確定他最終的位置;折半插入排序,比較次數是固定的,與初始排序無關;快速排序,初始排序不影響每次劃分時的比較次數,都要比較n次,但是初始排序會影響劃分次數,所以會影響總的比較次數,但快排平均比較次數最?。粴w并排序在歸并的時候,如果右路最小值比左路最大值還大,那么只需要比較n次,如果右路每個元素分別比左路對應位置的元素大,那么需要比較2*n-1次,所以與初始排序有關) - 精儉排序,即一對數字不進行兩次和兩次以上的比較,插入和歸并是“精儉排序”。插入排序,前面是有序的,后面的每一個元素與前面有序的元素比較,比較過的就是有序的了,不會再比較一次。歸并每次合并后,內部都是有序的,內部的元素之間不用再比較。選擇排序,每次在后面的元素中找到最小的,找最小元素的過程是在沒有排好序的那部分進行,所有肯定會比較多次。堆排序也需比較多次。
外部排序
生成合并段(run):讀入文件的部分記錄到內存->在內存中進行內部排序->將排好序的這些記錄寫入外存,形成合并段->再讀入該文件的下面的記錄,往復進行,直至文件中的記錄全部形成合并段為止。
外部合并:將上一階段生成的合并段調入內存,進行合并,直至最后形成一個有序的文件。
外部排序指的是大文件的排序,即待排序的記錄存儲在外存儲器上,待排序的文件無法一次裝入內存,需要在內存和外部存儲器之間進行多次數據交換,以達到排序整個文件的目的。外部排序最常用的算法是多路歸并排序,即將原文件分解成多個能夠一次性裝入內存的部分,分別把每一部分調入內存完成排序。然后,對已經排序的子文件進行多路歸并排序
不管初始序列是否有序, 冒泡、選擇排序時間復雜度是O(n^2),歸并、堆排序時間復雜度是O(nlogn)
外部排序的總時間 = 內部排序(產出初始歸并段)所需時間 + 外存信息讀取時間 + 內部歸并所需的時間
外排中使用置換選擇排序的目的,是為了增加初始歸并段的長度。減少外存讀寫次數需要減小歸并趟數
根據內存容量設若干個輸入緩沖區和一個輸出緩沖區。若采用二路歸并,用兩個輸入緩沖。
歸并的方法類似于歸并排序的歸并算法。增加的是對緩沖的監視,對于輸入,一旦緩沖空,要到相應文件讀后續數據,對于輸出緩沖,一旦緩沖滿,要將緩沖內容寫到文件中去。
外排序和內排序不只是考慮內外排序算法的性能,還要考慮IO數據交換效率的問題,內存存取速度遠遠高于外存。影響外排序的時間因素主要是內存與外設交換信息的總次數
例子
- 數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度為9的數組{1,2,3,2,2,2,5,4,2}。由于數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0
- 逆序數
- 加法的實現
-
最大K個數
some thoughts