計算機考研之數據結構-查找

數據結構-查找

概念

  1. 查找:在數據集合尋找知足某種條件的數據元素的過程。結果只有成功和失敗。
  2. 查找表(查找結構):用於查找的數據集合,通常由同種類型的數據元素構成。
  3. 靜態查找表:不會對查找表進行插入和刪除操做的查找表
  4. 動態查找表:回去查找表進行插入和刪除操做的查找表。
  5. 關鍵字:數據元素中惟一標識該元素的某個數據項的值。好比學生元素的學號項。
  6. 平均查找長度:查找過程當中平均比較關鍵字的次數。這是比較查詢性能的主要指標

\[ASL=\sum_{i=1}^n P_iC_i\]數據庫

其中P是查找低i個數據元素的機率,通常認爲機率相同即1/n。C是找到第i個數據元素所須要的比較次數。數據結構

線性查找

順序查找

對錶進行之間的掃描app

int Search(int a[], int len, int k){
    for(int i=0;i<len-1;i++)
        if(a[i]==k)
            return i;
    return -1;
}

\[ ASL_{成功}=\sum P_i(n-i+1)=\frac{n+1}{2} \]函數

\[ ASL_{失敗}=n+1 \]性能

折半查找

int Search(int a[], int len, int k){
    int low=0, high=len-1, mid;
    while(low<=high){
        mid=(low+high)/2;
        if(a[mid]==k) return mid;
        else if(a[mid]>k) high=mid-1;
        else low=mid+1;
    }
    return -1;
}

查找的過程相似於二叉查找樹,因此能夠引入二叉樹來描述,稱爲斷定樹

查找次數不會超過樹的深度。spa

\[ ASL= \frac { 1 } { n } \sum _ { i = 1 } ^ { n } l _ { i } = \frac { 1 } { n } \left( 1 \times 1 + 2 \times 2 + \cdots + h \times 2 ^ { h - 1 } \right) = \frac { n + 1 } { n } \log _ { 2 } ( n + 1 ) - 1 \approx \log _ { 2 } ( n + 1 )-1 \]3d

分塊查找

分塊查找將查找表分爲若干個塊,爲每一個區塊創建一個索引,肯定下標的上下界後使用順序查找。查找索引使用二分法。
指針

若索引查找和塊內查找的ASL爲Li和Ls,那麼分塊查找的ASL:code

\[ ASL=L_i+L_s \]

二叉查找樹

定義

二叉查找樹又稱二叉排序樹(BST)。是一種特殊性質的二叉樹,其中左子樹的結點的關鍵字都小於根結點,右子樹大於根結點。

typedef struct BNode{
    int key;
    struct BNode *lchild;
    struct BNode *rchild;
}BNode, *BTree;

查找

BNode* BSTSearch(BTree T, int key){
    if(T==NULL) return NULL;
    if(T->key==key) return T;

    if(key<T->key) return BSTSearch(T->lchild, key);
    else return BSTSearch(T->rchild, key);
}

插入

int BSTInsert(BTree& T, int key){
    if(T==NULL){
        T=(BTree)malloc(sizeof(BNode));
        T->key=key;
        T->lchild=T->rchild=NULL;
        return 1;
    }
    else if(key=T->key) return 0;
    else if(k<T->key) return BSTInsert(T->lchild, key);
    else return BSTInsert(T->rchild, key);
}

構造

void CreateBST(BTree &T, int[] a, int len){
    T=NULL;
    int i=0;
    while(i<n){
        BSTInsert(T, a[i]);
        i++;
    }
}

刪除

刪除這裏通常不會考代碼,知道有那麼三種狀況便可。

  1. 刪除結點爲葉結點,直接刪除。
  2. 刪除結點有一棵左子樹或右子樹,那麼直接讓子樹成爲刪除結點父結點的子樹。
  3. 刪除結點有左右兩棵子樹,並沿着左子樹的右指針一直向右或者右子樹的左指針一直向左,找到替代結點,將刪除結點與替代結點的值交換,此時狀況必定會變爲1或2,判斷並處理。

第三種狀況可能有點難理解,結合圖來理解一下。

二叉平衡樹

二叉搜索的搜索時間取決於樹的長度,因而可能會出現一種極端的狀況。

定義

二叉平衡樹(AVL)是一種刪除和插入結點時任意結點的左右子樹相差不會超過1的二叉搜索樹。這樣能夠加強搜索的性能。
定義平衡因子:左子樹和右子樹的高度差。
圖中結點的值爲其平衡因子:

插入

保證平衡的想法就是:當咱們插入或者刪除結點的時候,先檢查插入路徑上的結點的平衡因子的絕對值是否大於1。若是是,找到離插入點最近的一個絕對值大於1的結點,對其進行調整。
即咱們調整的對象都是最小的不平衡樹

對於平衡操做這裏分了四種狀況討論:

右單(LL)旋轉

狀況:A的左孩(L)的左子樹(L)插入新結點。
操做:B右旋(R),BR替換AL。

左單(RR)旋轉

狀況:A的右孩(R)的右子樹(R)插入新結點。
操做:B左旋(L),BL替換AR。

先左後右(LR)旋轉

狀況:A的左孩(L)的右子樹(R)
操做:將A的左孩子的右子樹的結點C(BR)先左後右旋轉到A。(注意這裏的操做對於的都是上面的操做的複合)。

先右後左(RL)旋轉

狀況:A的右孩(R)的左子樹(L)
操做:將A的右孩的左子樹的結點C(BL)先右後左旋轉A

記住全部調整都是爲了給插入結點空出位置來的,按照這個思路記憶會方便不少。

B樹

定義

B樹能夠視做爲二叉平衡樹的一種拓展,也稱多路平衡搜索樹
知足以下特性:(m做爲B樹的階數,通常≥3)

  1. 非葉結點的根結點至少有兩個子結點。
  2. 除根結點及葉結點最少[m/2]棵子樹,最多m 棵子樹。
  3. 全部非葉結點結構爲:\(n,P_0,K_1,P_1, \cdots, K_n,P_n\)
    1. K 升序排列的關鍵字。
    2. P 爲指向子樹結點的指針。
    3. n 爲結點關鍵字個數。
    4. \(p_i\)所指的子結點的全部關鍵字大於\(k_i\)且小於\(k_{i+1}\)
    5. \(p_0\)所指結點小於\(K_1\)\(p_n\)所指大於\(k_n\)
  4. 葉結點都處在同一層,且都爲空。

查找

B 樹的查找是跟二叉樹相似的多路查找。
分爲兩步:

  1. 在 B 樹內尋找結點。(磁盤中
  2. 在結點內尋找關鍵字。(內存中

因爲 B 樹通常用於數據庫中,即在根據指針在磁盤中找到結點並讀到內存中,再在內存中對有序表進行二分搜索,找不到就根據指針讀下一個結點,一直找到葉結點爲止。

插入

  1. 定位:利用上述的查找算法找到關鍵字最底層的某個非葉結點終端結點)。
  2. 插入:當插入關鍵字後若結點關鍵字數量大於 m-1 則對結點進行分裂,不然正常插入。

分裂

  1. 將插入關鍵字後的原結點從中間位置[m/2]分裂成兩個部分。
  2. 左邊置於原結點。
  3. 右邊置於新結點。
  4. 中間放到父結點。
  5. 檢查父結點是否溢出,若是溢出重複這個操做。

刪除

這裏分刪除的節點是否在終端節點來討論。
刪除的關鍵在於要使得結點的關鍵字數量≥[m/2]-1

終端節點上,分三類狀況:

  1. 直接刪除:結點內的關鍵字個數大於[m/2]-1,直接刪除。
  2. 兄弟夠借:結點內的關鍵字個數等於[m/2]-1,而且其左右兄中存在關鍵字個數大於[m/2]-1 的節點,則從中借關鍵字。
  3. 兄弟不夠借:若是 2 狀況中左右兄的節點都借不到關鍵字,則將關鍵字刪除後與左右兄或者雙親節點進行合併,這種合併操做可能會重複。

不在終端節點上,這種狀況要轉化爲上面那種狀況來討論:
即咱們將要刪除的非終端節點終端節點進行交換,再按終端節點的方式進行刪除。
這裏引入一個相鄰關鍵字的概念。對於不在終端節點上的結點來講,其相鄰關鍵字爲左子樹中值最大的關鍵字和右子樹中最小的關鍵字。
找相鄰關鍵字的方法與找二叉排序樹中前驅和後繼的方法相似。
沿着左子樹右指針或者右子樹左指針一直到終端結點就是相鄰關鍵字了。


B+樹

定義

一棵m階的 B+樹知足:

  1. 每一個結點最多有 m 棵子樹。
  2. 非葉根節點至少有2棵子樹,其餘至少有[m/2]棵子樹。
  3. 節點子樹個數與關鍵字個數相同
  4. 全部葉節點包含所有關鍵字以及指向相應記錄的指針,且關鍵字順序排列。
  5. 相鄰葉節點互相鏈接
  6. 全部分支節點都只是索引

區別

B+樹與 B 樹的主要區別在於:

  1. B+樹中 n 個關鍵字對於 n 個子樹,B 樹中 n 個關鍵字對應 n+1 個子樹。
  2. 在 B+樹中,只有葉節點包含信息。

散列表

概念

  1. 散列函數,將查找關鍵字映射成對應地址的函數。記爲 Hash(key)=Addr。
  2. 衝突/碰撞,散列函數將不一樣關鍵字映射到同一個地址的狀況。
  3. 散列表,根據關鍵字直接訪問的數據結構。

即理想狀況下,散列表的查找複雜度應該爲 O(1)。

散列函數構造

散列函數的構造要求:

  1. 散列函數的定義域必須包含全部要存儲的關鍵字。
  2. 散列函數的計算出來的地址應該等機率均勻地分佈在整個地址空間,減小衝突的發生。
  3. 散列函數應該儘可能簡單,容易計算。

下面是一些經常使用的散列函數:

  1. 直接定址法
    • \(H(key)=a\times key+b\)
    • 簡單而且不會衝突,可是會浪費空間。
  2. 除留餘數法
    • \(H(key)=key\%p\)

衝突解決

通常來講,散列函數不可能避免衝突,因此會對 key 進行再散列,用Hi表示第 i 次探測到的散列地址。

開放定址法

開放定址法即若是發生了衝突,能夠經過一個遞推公式一直遞推去找空閒的空間。
遞推公式爲:

\[H_i=(H(key)+d_i)\%m\]

其中 m 表示散列表表長,di爲增量的一個序列。

  1. 線性探測法
    • di=0,1,2,3,……,m-1。
    • 即衝突發送時,沿着表的下一項查找可用的空間。
    • 缺點在於這種方法容易發生堆積的問題。
  2. 平方探測法
    • di=1^2,2^2……
  3. 再散列法
    • di=Hash2(key)
    • 當衝突發生時,使用另一個散列函數進行再探測。
  4. 僞隨機序列法
    • di爲僞隨機序列。

在開放定值法的狀況下, 物理表中的元素不能隨意刪除,由於刪除元素會截斷其餘具備相同散列地址的元素的查找地址。暫時只能在邏輯上刪除。

拉鍊法

爲了不同義詞衝突,能夠把全部同義詞存儲在一個線性表中。
若是關鍵詞序列爲{19,14,23,01,68,20,84,27,55,11,10,79}
散列函數爲 H(key)=key%13。
拉鍊法圖示爲:



性能分析

散列表的性能通常取決於三個因素,散列函數,處理衝突,和裝填因子。
裝填因子 α = 表中記錄 n / 散列長度 m。

字符模式匹配

串的模式匹配即求模式串在主串中的位置。

簡單匹配

int Index(String S,String T){
    for(int i=0;i<T.len;i++)
        for(int j=0;j<S.len;j++){
            if(S[j]=T[i])
                if(j==S.len-1)
                    return TRUE;
            else
                break;
        }
    return FALSE;   
}

複雜度爲:O(n*m),n 和 m 分別爲主串和模式串的長度。

KMP匹配

void GetNext(String S, int next[]){
    int i=1,j=0;
    next[1]=0;
    while(i<S.len){
        if(j==0||S.ch[i]==S.ch[j]){
            i++;j++;
            next[i]=j;
        }
        else
            j=next[j];
    }
}
int KMP(String S, String T, int next[])
{
    int i=1;j=1;
    while(i<T.len && j<=S.len){
        if(j==0||T.ch[i]==S.ch[j]){
            i++;
            j++;
        }
        else{
            j=next[j];
        }
        if(j>S.len)
            return i-S.len;
        else 
            return 0;
    }
}

小結

習題

相關文章
相關標籤/搜索