大話西遊之王道考研數據結構第十講---查找

 

複習

  1. prim、kruskal、dijkstral複雜度分別是多少?
  2. 如何判斷一個圖是連通的?DFS遍歷一次輸出的是圖中什麼內容?
  3. 無向圖中刪除某個頂點的複雜度是多少(用鄰接鏈表存)?
  4. 是什麼造成拓撲排序不唯一?
  5. 關鍵路徑的五個函數都應該怎麼求?

一、查找

查找其實對我們來說並不陌生,我們在線性表中學過查找某個元素的複雜度是多少(順序表示是多少,鏈式表示是多少?),我們之前也學過二叉排序樹,二叉排序樹的構建其實就是爲了更方便的動態查找(二叉排序樹的查找複雜度是多少?)。這裏我們將系統的學習所有的查找方式。

在數據集合中尋找滿足某種條件的數據元素的過程稱爲查找。查找的結果一般分爲兩種,查找成功和查找失敗(還記得我們在二叉排序樹裏面,計算了兩種平均查找長度 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;
}

二分查找的圖表示其實就是一顆平衡二叉樹

ASL = (1/n)(1*1 + 2*2 + 4*3 +...+h*2^{h-1}) = ((n+1)/n)log_2(n+1)-1 \approx log_2(n+1)-1

這裏面的計算用到了高數裏面無窮級數那一章裏面的內容,剛好可以把那一章複習一下,並且終於明白那一章不僅難,還有點用。。。

所以折半查找的複雜度是O(log_2n),而順序查找是O(n),所以折半查找好一點

1.4 分塊查找

分塊查找結合了順序查找和折半查找的優點,需要另外開闢一個索引表,把n個元素分爲k塊,索引表長度爲k,索引表中的元素,代表對應塊中最大元素。在索引部分採用折半查找,內部採用順序查找。

所以總共的平局查找長度應該分爲兩部分,設索引查找和塊內查找的平均查找長度爲L_I,L_S

ALS = L_I+L_S

設將長度爲n的超找表均勻的分爲b塊,每塊有s個記錄,在等概率的情況下,若在塊內和索引表中均採用順序查找,則平均查找長度爲:

ALS = L_I+L_S = (b+1)/2 + (s+1)/2 = (s^2 +2s+n)/2s

此時,若s = \sqrt n 則式子可以取到最小值:\sqrt n + 1

如果對索引採用折半查找時候,平均長度爲:

ALS = L_I+L_S = \left \lceil log_2 (b+1) \right \rceil+ (s+1)/2

二、散列表(Hash表)(大題必考)

如果我們想查詢一個表裏有沒有元素k,如果這個表是無序的,那麼複雜度應該是O(n),就算是有序的,順序查找是O(n),折半查找是O(log_2n)。這些都是基於比較的查找方式,查找的效率取決於比較的次數,那麼有沒有可能讓他變成O(1)呢?

Hash的意義在於把一個大的集合A,映射到小的集合B,這樣查找起來會更省時。哈希表是根據關鍵字直接進行訪問的數據結構。我們通過散列函數,把關鍵字映射到對應的函數中。散列函數可能會把兩個或兩個以上的不同關鍵字映射到同一地址,稱這樣的情況爲“衝突”。

1.1 散列函數的構造方法

           除留餘數法:假定散列表的表長爲m,取一個不大於m但最接近或等於m的質數p,利用一下公式吧關鍵字轉換成散列地址。散列函數爲

                                                                                                  H(key) = key\%p

1.2 處理衝突的方法

1. 開放定址法

            這個需要把數據放在數組裏面

                                                                                           H_1 = (H(key) + d_i) \%m

           這個d的確定有好幾種方法,比如d每次增加1,或者每次變爲平方,或者換個哈希函數,或者隨機選一個。

a.線性探測法:發現衝突時候,一個一個往後挪,直到找到空位子坐下~但是這樣會造成大量元素在相鄰的散列地址上"聚集”(堆積),大大降低查找效率。

b.平方探測法:d_i = 1^2,-1^2......k^2,-k^2,其可以避免堆積,但是不能探測到散列表上所有單元,但至少能探測到一半單元。

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

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章