複習
- prim、kruskal、dijkstral複雜度分別是多少?
- 如何判斷一個圖是連通的?DFS遍歷一次輸出的是圖中什麼內容?
- 無向圖中刪除某個頂點的複雜度是多少(用鄰接鏈表存)?
- 是什麼造成拓撲排序不唯一?
- 關鍵路徑的五個函數都應該怎麼求?
一、查找
查找其實對我們來說並不陌生,我們在線性表中學過查找某個元素的複雜度是多少(順序表示是多少,鏈式表示是多少?),我們之前也學過二叉排序樹,二叉排序樹的構建其實就是爲了更方便的動態查找(二叉排序樹的查找複雜度是多少?)。這裏我們將系統的學習所有的查找方式。
在數據集合中尋找滿足某種條件的數據元素的過程稱爲查找。查找的結果一般分爲兩種,查找成功和查找失敗(還記得我們在二叉排序樹裏面,計算了兩種平均查找長度 ASL成功和ASL失敗,還記的ASL失敗是怎麼計算的麼?)。
1.1 順序查找
這個其實就是線性表的查找方式,
int Search_Seq(int a[],int n, int key){
for(int i =0;i<n;i++){ //下標從0開始
if(a[i] == key)
return i;
}
return -1;
}
int Search_Seq(int a[],int n, int key){
//a數組下標從1開始,這時候我們可以把a[0]利用起來
a[0] = key;
for(int i = n;a[i]!=key;i--){ //下標從0開始
return i;
}
這裏提供兩種查找代碼,第一種是我們一般採用的方式,第二種是書上提供的更騷的套路。第二種理解下這麼妖嬈的操作是怎麼實現的。二者在複雜度上面沒有變化,但是代碼執行次數上面,第二種更快一點(就像 3*n 和 2*n 的複雜度都是n一樣)。下面的ASL都是按照第二種查找方式計算的
ASL_成功 = (n+1)/2;
(假設每個元素查找概率相同,第一個元素查找1次 第n個元素查找n次,總共查找 ((1+n)*n/)2,平均的話再/n就好了)
ASL_失敗 = n+1
(因爲查找失敗時候,我們需要查找到第0個元素,所以總共對比的元素有n+1個,如果按照第一種方式,查找失敗是多少?,考試時候,正確答案是第二種的查找,第一種就不要記了,這裏加上第一種只是爲了更深刻的理解查找失敗長度計算過程)
1.2 有序表的順序查找
我們待查找的序列是有序的,假設是遞增有序,這就相當於,二叉排序樹裏面只有左結點了。這時候查找成功和失敗就是二叉排序查找成功和失敗的方式:
ASL_成功 = (n+1)/2;
ASL_失敗 = (1+2+3+...+n+n)/n = n/2 + n/(n+1)
有序查找可以是順序表示,也可以是鏈式表示
1.3 二分查找(也是折半查找)
它和順序查找一樣,也要求查找表示有序的,但是它只適用於順序表,因爲他需要找到查找表裏面,中間的元素,如果是鏈式表的話,這樣找到中間元素都是O(n),而順序表示是O(1)。所以只適用於順序表。
其核心思想和高中學的最小二乘法逼近函數答案差不多,待查找的查找表a的區間是[l,r],最中間的是mid = (l+r)/2 我們看下a[mid]和要查找的元素大小比較,如果小的話,說明應該落在[mid+1,r]裏面,如果大的話因該落在[l,mid-1]裏面,如果相等的話直接輸出
void Binary_Find(int t,int n)
{
int l = 1;
int r = n;
int mid;
while(r>=l)
{
mid = (l+r)/2;
if(a[mid]<t)
{
l = mid+1;
}
else if(a[mid]>t)
{
r = mid-1;
}
}
a[r+1] = t;
}
二分查找的圖表示其實就是一顆平衡二叉樹
這裏面的計算用到了高數裏面無窮級數那一章裏面的內容,剛好可以把那一章複習一下,並且終於明白那一章不僅難,還有點用。。。
所以折半查找的複雜度是,而順序查找是,所以折半查找好一點
1.4 分塊查找
分塊查找結合了順序查找和折半查找的優點,需要另外開闢一個索引表,把n個元素分爲k塊,索引表長度爲k,索引表中的元素,代表對應塊中最大元素。在索引部分採用折半查找,內部採用順序查找。
所以總共的平局查找長度應該分爲兩部分,設索引查找和塊內查找的平均查找長度爲
設將長度爲n的超找表均勻的分爲b塊,每塊有s個記錄,在等概率的情況下,若在塊內和索引表中均採用順序查找,則平均查找長度爲:
此時,若 則式子可以取到最小值:
如果對索引採用折半查找時候,平均長度爲:
二、散列表(Hash表)(大題必考)
如果我們想查詢一個表裏有沒有元素k,如果這個表是無序的,那麼複雜度應該是O(n),就算是有序的,順序查找是O(n),折半查找是。這些都是基於比較的查找方式,查找的效率取決於比較的次數,那麼有沒有可能讓他變成O(1)呢?
Hash的意義在於把一個大的集合A,映射到小的集合B,這樣查找起來會更省時。哈希表是根據關鍵字直接進行訪問的數據結構。我們通過散列函數,把關鍵字映射到對應的函數中。散列函數可能會把兩個或兩個以上的不同關鍵字映射到同一地址,稱這樣的情況爲“衝突”。
1.1 散列函數的構造方法
除留餘數法:假定散列表的表長爲m,取一個不大於m但最接近或等於m的質數p,利用一下公式吧關鍵字轉換成散列地址。散列函數爲
1.2 處理衝突的方法
1. 開放定址法
這個需要把數據放在數組裏面
這個d的確定有好幾種方法,比如d每次增加1,或者每次變爲平方,或者換個哈希函數,或者隨機選一個。
a.線性探測法:發現衝突時候,一個一個往後挪,直到找到空位子坐下~但是這樣會造成大量元素在相鄰的散列地址上"聚集”(堆積),大大降低查找效率。
b.平方探測法:,其可以避免堆積,但是不能探測到散列表上所有單元,但至少能探測到一半單元。
c.再散列法:發現衝突以後,通過另一個函數,重新選取位置,直到不發生衝突爲止。
2.拉鍊法
這個就有點像鏈表了,所以你會發現數據結構過來過去,存儲方式就這倆種,很多地方都是通的。
1.3 ASL成功和失敗
1.哈希表中一共有四個主要參數,p,n,m,a。其中p是待取模的質數,m是整個表的長度,n是元素的個數,a是裝填因子
2.哈希表採用開放地址法時候,查找失敗的分母取決於p而不是m。
2.哈希的查找性能,即平均查找長度依賴於哈希表的裝填因子a,不直接依賴於 n 或 m。
3.拉鍊法中,查找失敗有兩種不同的計算方式,這裏我們採用教材規定的上面那種。
三、字符串匹配
字符串匹配的含義就是檢測要查找的字符串T在不在被查找的字符串S裏面。這就和word裏面的ctrl+f的功能一樣(這個功能可能是用優化以後的KMP或者SUNDAY、BM算法寫的,SUMDAY是一個非常好理解的,且效率比KMP要高的算法,如果你喜歡這一塊的話,可以瞭解下這個算法)。
這裏,容易想到的一個簡單的算法就是一個一個比算法,在S中挨個查找,如果某個位置往後和T能夠完全匹配,就返回這個位置,如果不行,就看下這個位置的下一位,直到S字符串全部檢查完畢。
3.1 簡單的模式匹配算法
int Index(char s[],int sum_s,char t[],int sum_t){
int i = 1,j = 1;
while(i <=sum_s&&j<=sum_t){
if(s[i] == t[j] ){
i++;
j++;
}
else{
i = i-j+2;
j =1;
}
}
if(j >= sum_t){
return i - sum_t;
}
else return 0;
}
3.2 改進的模式匹配算法-KMP算法
KMP算法算是本科裏面學到過的最難理解的算法之一了,當初也是花了很長時間去學習這個東西,那個時候雖然搞懂了,但是現在又給忘了,而KMP剛好考研時候只考NEXT數組的計算方式(完全弄懂KMP對於只應付我們學校初試來說,沒有太大必要)。NEXT數組裏面:
1.next[1] = 0,next[2] = 1
2.後面每一個來說,只需要看看其前面的數組裏面,前綴後綴重複的最大長度是多少,然後加1就好了,比如計算NEXT[6]時候,前面最大前綴是ABA,後綴是ABA 所以NEXT[6]= 3+ 1 = 4
編號 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
S |
A |
B |
A |
B |
A |
A |
A |
B |
A |
B |
A |
A |
Next |
0 |
1 |
1 |
2 |
3 |
4 |
2 |
2 |
3 |
4 |
5 |
6 |