第9章 檢索

第9章 檢索

1、檢索的基本概念

  1. 檢索:肯定數據元素集合中是否存在數據元素等於特定元素或是否存在元素知足某種給定特徵的過程

2、線性表的檢索

2.1 順序檢索

  1. 注:暴力搜索,很少贅述
  2. 順序檢索時平均查找次數:\(ASL_{seq}=(n+1)/2\)

2.2 二分法檢索(折半查找)

  1. 線性表結構:二分法檢索須要線性表結點已經按其關鍵字從小到大(或從大到小)排序
  2. 二分法檢索時平均查找次數:\(ASL_{bins}\approx{log_2(n+1)-1}\)

2.2.1 二分法檢索(非遞歸實現)(真題)(算法)

  1. 算法步驟:
    1. 獲取二分以後的中間結點的序號 \(mid\)
    2. 讓待查找的數據元素 \(key\) 和中間結點 \(a[mid]\) 比較,成功則直接返回
    3. 失敗以後,判斷數據元素和中間結點的大小
      1. 若是中間結點大於數據元素,則在前半部分繼續二分檢索,\(right\) 變成 \(mid-1\)\(mid-1\)是由於 \(mid\) 已經作出過判斷,不須要再次比較)
      2. 若是中間結點小於數據元素,則在後半部分繼續二分檢索,\(left\) 變成 \(mid+1\)
int binsearch(int a[], int left, int right, int x) {
    int mid;
    while (left <= right) {
        mid = (left + right) / 2; // 二分
        if (a[mid] == x) return mid; // 檢索成功返回
        if (a[mid] > x) right = mid - 1; // 繼續在前半部分進行二分檢索
        else left = mid + 1; // 繼續在後半部分進行二分檢索
    }
    return -1; // 當 left>right 時表示查找區間爲空,檢索失敗
}

2.3 分塊檢索

  1. 分塊檢索思想:把線性表分紅若干塊,每一塊中,結點的存放不必定有序,但塊與塊之間必須是分塊有序的(第一塊中的結點的值都小於第二塊中的結點值;第二塊中的結點值都小於第三塊中的結點值…)
  2. 分塊查找時平均查找長度爲:假設線性表中共有 \(n\) 個元素,且被均分紅 \(b\) 塊,則每塊中的元素個數 \(s=n/b\),待查元素在索引表中的平均查找長度爲 \(E_1\),塊內查找時所需的平均查找長度爲 \(E_b\)
    1. 在順序檢索來肯定塊時,分塊查找成功時的平均查找長度爲 \(ASL_{ids}=E_1+E_b=\frac{b+1}{2}+\frac{s+1}{2}=\frac{n/s+s}{2}+1=\frac{n+s^2}{2s}+1\)
      1. \(s=\sqrt{n}\) 時,\(ASL_{ids}\) 取最小值 \(\sqrt{n}+1\) (最佳查找長度)
    2. 在二分檢索來肯定塊時,分塊查找成功時的平均查找長度爲 \(ASL'_{ids}=E_1+E_b\approx{log_2(b+1)}-1+\frac{s+1}{2}\approx{log_2(\frac{n}{s}+1)+\frac{s}{2}}\)
  3. 算法步驟:
    1. 創建索引表(數組):
      1. 索引表結點中存儲兩個元素:一個元素表示某一塊在原數組中的開始下標;另外一個元素表示某一塊中最大的值
    2. 讓被查找值和最大的值進行比較,獲取查找值在數組中比較的下標範圍
    3. 最後在該範圍內進行順序查找便可
  4. 圖分塊檢索:

2.3.1 分塊檢索索引表存儲結構

typedef int datatype;
// 索引表結點類型
typedef struct {
    datatype key;
    int address;
} indexnode;

3、二叉排序樹

  1. 二分檢索法的缺陷:二分檢索法雖然有較高的效率,可是要求被查找的一組數據有序,所以在這一組數據中增添刪除很麻煩
  2. 二叉排序樹解決的問題:在檢索過程當中不須要被查找的數據有序,便可擁有較高的查找效率,實如今數據查找過程當中的增添和刪除
  3. 二叉排序樹的性質:
    1. 左子樹非空時,左子樹上的全部結點的值都小於根結點的值
    2. 右子樹非空時,右子樹上的全部結點的值都大於根結點的值
    3. 它的左、右子樹自己又各是一顆二叉排序樹
  4. 注:當二叉排序樹只有左(右)結點時,退化成一個有序的單鏈表(此時應該用二分檢索法進行檢索)
  5. 注:對二叉排序樹進行中序遍歷時能夠獲得按結點值遞增排序的結點序列
  6. 注:不一樣關鍵碼構造出不一樣的二叉排序樹的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)
  7. 經常使用操做:
    1. 基於二叉排序樹的結點的刪除

3.1 二叉排序樹的存儲結構

typedef int datatype;
// 二叉排序樹結點定義
typedef struct node {
    datatype key; // 結點值
    struct node *lchild, *rchild; // 左、右孩子指針
} bsnode;
typedef bsnode *bstree;

3.2 基於二叉排序樹檢索算法(算法)

  1. 算法步驟:
    1. 當二叉樹爲空樹時,檢索失敗
    2. 若是二叉排序樹根結點的關鍵字等於待檢索的關鍵字,則檢索成功
    3. 若是二叉排序樹根結點的關鍵字小於待檢索的關鍵字,則用相同的方法繼續在根結點的右子樹中檢索
    4. 若是二叉排序樹根結點的關鍵字大於待檢索的關鍵字,則用相同的方法繼續在根結點的左子樹中檢索
typedef int datatype;
// 二叉排序樹結點定義
typedef struct node {
    datatype key; // 結點值
    struct node *lchild, *rchild; // 左、右孩子指針
} bsnode;
typedef bsnode *bstree;

// 遞歸實現
void bssearch1(bstree t, datatype x, bstree *f, bstree *p) {
    // *p 返回 x 在二叉排序中的地址;*f 返回 x 的父結點位置
    *f = NULL;
    *p = t;
    while (*p) {
        if (x == (*p)->key) return;
        *f = *p;
        *p = (x < (*p)->key) ? (*p)->lchild : (*p)->rchild;
    }
    return;
}

// 非遞歸實現
bstree bssearch2(bstree t, datatype x) {
    if (t == NULL || x == t->key) return t;
    if (x < t->key) return bssearch2(t->lchild, x); // 遞歸地在左子樹檢索
    else return bssearch2(t->rchild, x); // 遞歸地在右子樹檢索
}

3.3 基於二叉排序樹的結點的插入算法(算法)

  1. 算法步驟:
    1. 循環查找插入位置的結點
    2. 若二叉排序樹爲空,則生成一個關鍵字爲 \(x\) 的新結點,並令其爲二叉排序樹的根結點
    3. 不然,將待插入的關鍵字 \(x\) 與根結點的關鍵字進行比較,若兩者相等,則說明樹中已有關鍵字 \(x\),無須插入
    4. \(x\) 小於根結點的關鍵字,則將 \(x\) 插入到該樹的左子樹中,不然將 \(x\) 插入到該樹的右子樹
    5. \(x\) 插入子樹的方法與在整個樹中的插入方法是相同的,如此進行下去,直到 \(x\) 做爲一個新的葉結點的關鍵字插入到二叉排序樹中,或者直到發現樹中已有此關鍵字爲止
typedef int datatype;
// 二叉排序樹結點定義
typedef struct node {
    datatype key; // 結點值
    struct node *lchild, *rchild; // 左、右孩子指針
} bsnode;
typedef bsnode *bstree;

void insertbstree(bstree *t, datatype x) {
    bstree f = NULL, p;
    p = *t;

    // 查找插入位置
    while (p) {
        if (x == p->key) return; // 若二叉排序中已有 x,無需插入
        f = p; // *f 用於保存新結點的最終插入位置
        p = (x < p->key) ? p->lchild : p->rchild;
    }

    // 生成待插入的新結點
    p = (bstree) malloc(sizeof(bsnode));
    p->key = x;
    p->lchild = p->rchild = NULL;

    // 原樹爲空
    if (*t == NULL) *t = p;
    else if (x < f->key) f->lchild = p;
    else f->rchild = p;
}

3.4 生成一顆排序二叉樹

  1. 算法步驟:
    1. 循環輸入關鍵字,而後使用 \(3.3\) 的插入算法把關鍵字插入二叉排序樹
  2. 圖生成二叉排序樹:

4、豐滿樹和平衡樹

  1. 豐滿樹和平衡樹解決的問題:保證在樹中插入或刪除結點時保持樹的 「平衡」,使之儘量保持二叉樹的性質又保證樹的高度儘量地爲 \(O(log_2n)\)

4.1 豐滿樹(大綱未規定)

  1. 豐滿樹:任意兩個非雙孩子結點的高度之差的絕對值要小於等於 \(1\),即子女結點個數小於 \(2\) 的結點只出如今樹的最低兩層中
  2. 經常使用操做:
    1. 創建豐滿樹

4.2 平衡二叉排序樹

  1. 平衡二叉樹(\(AVL\) 樹 ):它的左子樹和右子樹都是平衡二叉樹,**且左子樹和右子樹的高度之差的絕對值不超過 \(1\) **
  2. 平衡因子:結點的左子樹高度與右子樹高度之差(平衡二叉樹的任意結點的平衡因子絕對值小於等於 \(1\)
  3. 豐滿樹和平衡樹:豐滿樹必定是平衡樹,平衡樹卻不必定是豐滿樹
  4. 經常使用操做:
    1. 基於 \(AVL\) 樹 的結點插入算法
  5. 平衡二叉排序樹最大深度求法:假設 \(N_h\) 表示深度爲 \(h\) 的平衡二叉樹中含有的最少的結點數目,那麼,\(N_0=0\)\(N_1=1\)\(N_2=2\),而且 \(N_h=N_{h-1}+N_{h-2}+1\)

4.3 AVL樹調整平衡的方法

4.3.1 LL型平衡旋轉

  1. \(LL\) 型 平衡旋轉:node

    1. 因爲在 \(A\) 的左孩子的左子樹上插入新結點,使 \(A\) 的平衡度由 \(1\) 增至 \(2\) ,導致以 \(A\) 爲根的子樹失去平衡,如圖\(9.9(a)\)
    2. 示此時應進行一次順時針旋轉,「提高」 \(B\)\(A\) 的左孩子)爲新子樹的根結點
    3. \(A\) 降低爲 \(B\) 的右孩子
    4. \(B\) 原來的右子樹 \(B_r\) 調整爲 \(A\) 的左子樹
  2. 圖LL型平衡旋轉算法

4.3.2 RR型平衡旋轉

  1. \(RR\) 型 平衡旋轉:數組

    1. 因爲在 \(A\) 的右孩子的右子樹上插入新結點,使A的平衡度由 \(-1\) 變爲 \(-2\),導致以 \(A\) 爲根的子樹失去平衡,如圖 \(9.9(b)\) 所示
    2. 此時應進行一次逆時針旋轉,「提高」 \(B\)\(A\) 的右孩子)爲新子樹的根結點
    3. \(A\) 降低爲 \(B\) 的左孩子
    4. \(B\) 原來的左子樹 \(B_L\) 調整爲 \(A\) 的右子樹
  2. 圖RR型平衡旋轉app

4.3.3 LR型平衡旋轉

  1. \(LR\) 型 平衡旋轉(\(A\) 的左孩子的右孩子(\(LR\))插入 \(A\)\(A\) 的左孩子 \(B\) 之間,以後作 \(LL\) 型 平衡旋轉):函數

    1. 因爲在 \(A\) 的左孩子的右子樹上插入新結點,使 \(A\) 的平衡度由 \(1\) 變成 \(2\),導致以 \(A\) 爲根的子樹失去平衡,如圖 \(9.9(c)\) 所示
    2. 此時應進行兩次旋轉操做(先逆時針,後順時針),即 「提高」 \(C\)\(A\) 的左孩子的右孩子)爲新子樹的根結點
    3. \(A\) 降低爲 \(C\) 的右孩子
    4. \(B\) 變爲 \(C\) 的左孩子
    5. \(C\) 原來的左子樹 \(C_L\) 調整爲 \(B\) 如今的右子樹
    6. \(C\) 原來的右子樹 \(C_r\) 調整爲 \(A\) 如今的左子樹
  2. 圖LR型平衡旋轉編碼

4.3.4 RL型平衡旋轉

  1. \(RL\) 型 平衡旋轉(\(A\) 的右孩子的左孩子(\(RL\))插入 \(A\)\(A\) 的右孩子 \(B\) 之間,以後作 \(RR型\) 平衡旋轉):spa

    1. 因爲在 \(A\) 的右孩子的左子樹上插入新結點,使A的平衡度由 \(-1\) 變成 \(-2\),導致以 \(A\) 爲根的子樹失去平衡,如圖 \(9.9(d)\)所示
    2. 此時應進行兩旋轉操做(先順時針,後逆時針),即 「提高」 $ C$(即 \(A\) 的右孩子的左孩子)爲新子樹的根結點
    3. \(A\) 降低 \(C\) 的左孩子
    4. \(B\) 變爲 \(C\) 的右孩子
    5. \(C\) 原來的左子樹 \(C_L\) 調整爲 \(A\) 如今的右子樹
    6. \(C\) 原來的右子樹 \(C_r\) 調整爲 \(B\) 如今的左子樹。
  2. 圖RL型平衡旋轉設計

4.4 生成一顆平衡二叉排序樹

  1. 算法步驟:3d

    1. 插入:不考慮結點的平衡度,使用在二叉排序樹中插入新結點的方法,把結點 \(k\) 插入樹中,同時置新結點的平衡度爲 \(0\)
    2. 調整平衡度:假設 \(k0,k1,…,km=k\) 是從根 \(k_0\) 到插入點 \(k\) 路徑上的結點,因爲插入告終點 \(k\),就須要對這條路徑上的結點的平衡度進行調整(調整平衡度參考上述四種(\(LL、RR、LR、RL\))方法)
    3. 改組:改組以 \(k_j\) 爲根的子樹除了知足新子樹高度要和原來以 \(k_j\) 爲根子樹的高度相同外,還需使改造後的子樹是一棵平衡二叉排序樹
  2. 圖生成一顆AVL樹指針

5、二叉排序樹和Huffman樹

5.1 擴充二叉樹(大綱未規定)

5.2 二叉排序樹(大綱未規定)

5.3 Huffman樹

  1. 帶權外部路徑長度:\(WPL=\sum_{i=1}^{n}W_{ki}*(\lambda{k_i})\)
    1. \(n\) 個結點 \(k_1,k_2,\cdots,k_n\),它們的權分別是 \(W(k_i)\)
    2. \(\lambda{k_i}\) 是從根結點到達外部結點 \(k_i\) 的路徑長度
  2. \(huffman\) 樹:具備最小帶權外部路徑長度的二叉樹
  3. 算法步驟:
    1. 根據給定的 \(n\) 個權值 \(\{w_1,w_2,\cdots,w_n\}\) 構造 \(n\) 棵二叉樹的集合 \(F=\{T_1,T_2,\cdots,T_n\}\),其中每棵二叉樹 \(T_i\) 中只有一個帶權爲 \(w_i\) 的根結點,其左、右子樹均爲空
    2. \(F\) 中選取兩棵根結點的權值最小的樹做爲左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點權值爲其左、右子樹根結點的權值之和
    3. \(F\) 中用新獲得的二叉樹代替這兩棵樹
    4. 重複步驟 \(二、3\),直到 \(F\) 中只含有一棵樹爲止

5.3.1 構造Huffman樹

  1. 對於結點序列 \(六、十、1六、20、30、24\) ,構造 \(huffman\) 樹的過程以下:
  2. 圖構造huffman樹:

5.3.2 經過Huffman算法構造編碼樹

  1. 注:出現頻率越大的字符其編碼越短

  2. 圖huffman編碼:

  3. 字符平均編碼長度爲:\(((6+10)*4+16*3+(20+24+30)*2)/106=2.45\)

6、B樹

  1. B樹:稱爲多路平衡查找樹,也稱爲 「B-樹」,主要針對較大的、存放在外存儲器上的文件,適合在磁盤等直接存取設備上組織動態的索引表

6.1 B-樹的定義

  1. B-樹:一種平衡的多路查找樹,一顆 \(m(m\geq{3})\) 階的B-樹,或爲空樹,或爲知足下列特性的 \(m\) 叉樹
    1. 樹中的每一個結點至多有 \(m\) 棵子樹
    2. 若根結點不是葉子結點,則至少有兩棵子樹
    3. 全部的非終端結點中包含下列信息 \((sn,p_0,k_1,p_1,k_2,p_2,\ldots,k_n,p_n)\)
      1. 其中 \(k_i(1\leq{i}\leq{n})\) 爲關鍵字,且 \(k_i<k_i+1(1\leq{i}\leq{n})\)
      2. \(p_j(0\leq{j}\leq{n})\) 爲指向子樹根結點的指針,且 \(p_j(0\leq{j}<n)\) 所指子樹中全部結點的關鍵字均小於 \(k_j+1\)
      3. \(p_n\) 所指子樹中全部結點的關鍵字均大於 \(k_n\)
      4. \(n(\lceil{m/2}\rceil-1\leq{n}\leq{m-1})\) 爲關鍵字的個數(\(n+1\) 爲子樹個數)
    4. 除根結點以外全部非終端結點至少有 棵子樹,也即每一個非根結點至少應有 \(\lceil{m/2}\rceil-1\) 個關鍵字
    5. 全部的葉子結點都出如今同一層上,而且不帶信息(能夠看做是外部結點或查找失敗的結點,實際上這些結點不存在,指向這些結點的指針爲空)
  2. 圖3階B-樹:

6.2 B-樹的基本操做

  1. 基於B-樹的查找
  2. 基於B-樹的插入運算
  3. 基於B-樹的刪除運算

6.3 B+樹(大綱未規定)

7、散列表檢索

7.1 散列存儲

  1. 散列存儲的基本思想:以關鍵碼的值爲變量,經過必定的函數關係(稱爲散列(\(Hash\))函數),計算出對應的函數值,以這個值做爲結點的存儲地址
  2. 衝突:兩個不一樣的關鍵字具備相同的存放地址
  3. 負載因子:\(\frac{散列表中結點的數目}{基本區域能容納的結點樹}\)
    1. 注:負載因子越小,空間浪費越多;負載因子越大,衝突可能性越高(負載因子大於 \(1\) 時,必定會有衝突)

7.2 散列函數的構造

  1. 除餘法(大機率):使用略小於 \(Hash\) 地址集合中地址個數 \(m\) 的質數 \(p\) 來除關鍵字
    1. 散列函數: \(H(key) = key\%p\)
    2. 注:除餘法的 \(p\) 的選擇不當,容易發生衝突
  2. 平方取中法:取關鍵字平方後的中間幾位爲 \(Hash\) 地址,所取的位數和 \(Hash\) 地址位數相同
  3. 摺疊法:讓關鍵字分割成位數相同的幾部分,而後選取這幾部分的疊加和做爲 \(Hash\) 地址
  4. 數字分析法:對於關鍵字的位數比存儲區域的地址碼位數多的狀況下,對關鍵字分析,丟掉分佈不均勻的位,留下分佈均勻的位做爲 \(Hash\) 地址
  5. 直接地址法(大機率):取關鍵字或關鍵字的某個線性函數值爲哈希地址
    1. 散列函數:\(H(key)=key\,或\,H(key)=a*key+b\)
    2. 注:直接地址法對於不一樣的關鍵字,不會產生衝突,容易形成空間的大量浪費

7.3 衝突處理

  1. 開放定址法:發生衝突時,按照某種方法繼續探測基本表中的其餘存儲單元,直到找到一個開放的地址(空位置)爲止

    1. 開放定址法的通常形式:\(H_i(k) = (H(k)+d_i)\,mod\,m\),其中 \(H(k)\) 爲關鍵字爲 \(k\) 的直接哈希地址,\(m\) 爲哈希表長,\(d_i\) 爲每次再探測時的地址增量
      1. 線性探測再散列:\(d_i = 1,2,3,\cdots,m-1\)
      2. 二次探測再散列:\(d_i=1^2,{-1}^2,2^2,{-2}^2,\cdots,k^2,{-k}^2(k\leq{m/2})\)
      3. 隨機探測再散列:\(d_i = 隨機數序列\)
    2. 彙集:幾個 \(Hash\) 地址不一樣的關鍵字爭奪同一個後繼 \(Hash\) 地址的現象
    3. 開放定址法容易發生彙集現象,尤爲是採用線性探測再散列
    4. 圖開放定址法:
  2. 再哈希法:某個元素 \(k\) 在原散列函數 \(H(k)\) 的映射下與其餘數據發生碰撞時,採用另一個 \(Hash\) 函數 \(H_i(k)\) 計算 \(k\) 的存儲地址

    1. 注:該方法不容易發生 「彙集」,但增長了計算的時間
  3. 拉鍊法:把全部關鍵字爲同義詞的結點連接在同一個單鏈表中,若選定的散列表長度爲 \(m\),則能夠把散列表定義爲一個由 \(m\) 個頭指針組成的指針數組 \(T[0\ldots{m-1}]\)凡是散列地址爲 \(i\) 的結點,均插入到以 \(T[i]\) 爲頭指針的單鏈表中

    1. 注:拉鍊法的指針須要額外的空間,所以當結點規模較小時,開放定址法更加節省空間
    2. 圖拉鍊法:
  4. 開放定址法、再哈希法、拉鍊法的比較

    1. 開放定址法 再哈希法 拉鍊法
      優勢 不須要額外的計算時間和空間 不易 「彙集」 無 「彙集」;非同義詞不會衝突
      缺點 容易發生 「彙集」 現象 增長了計算時間 須要額外的空間

7.4 散列表檢索的應用

  1. 將關鍵字序列 \((七、八、30、十一、1八、九、14)\) 散列存儲到散列表中。散列表的存儲空間是一個下標從 \(0\) 開始的一維數組。散列函數爲: \(H(key) = (key*3) MOD 7\),處理衝突採用線性探測再散列法,要求裝載因子爲 \(0.7\)

    1. 請畫出所構造的散列表
    2. 分別計算等機率狀況下查找成功和查找不成功的平均查找長度

8、查找算法的分析及應用(書中未給出)

9、算法設計題

9.1 在給定查找樹中刪除根結點值爲 \(a\) 的子樹(算法)

\(T\) 是一棵給定的查找樹,試編寫一個在樹 \(T\) 中刪除根結點值爲 \(a\) 的子樹的程序。要求在刪除的過程當中釋放該子樹中全部結點所佔用的存儲空間。這裏假設樹 T 中的結點採用二叉鏈表存儲結構

  1. 算法步驟:
    1. 注:刪除二叉樹能夠採用後序遍歷方法,先刪除左子樹,再刪除右子樹,最後刪除根結點
    2. 先在指定的樹中查找值爲 a 的結點,找到後刪除該棵子樹
typedef int datatype;
// 二叉樹結點定義
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

// 刪除以 t 爲根的二叉樹
void deletetree(bintree *t) {
    if (*t) {
        deletetree(&(*t)->lchild); // 遞歸刪除左子樹
        deletetree(&(*t)->rchild);// 遞歸刪除右子樹
        free(*t); // 刪除根結點
    }
}

// 刪除二叉樹中以根結點值爲 a 的子樹
void deletea(bintree *t, datatype a) {
    bintree pre = NULL, p = *t;

    // 查找值爲 a 的結點
    while (p && p->data != a)
    {
        pre = p;
        p = (a < p->data) ? p->lchild : p->rchild;
    }
    
    if (!pre) *t = NULL;    // 樹根
    else  // 非樹根
    if (pre->lchild == p) pre->lchild = NULL;
    else pre->rchild = NULL;
    deletetree(&p); // 刪除以 p 爲根的子樹
}

9.2 判斷給定二叉樹是否爲二叉排序樹(算法)

試寫一算法判別給定的二叉樹是否爲二叉排序樹,設此二叉樹以二叉鏈表爲存儲結構,且樹中結點的關鍵字均不相同

  1. 算法步驟:
    1. 斷定二叉樹是否爲二叉排序樹能夠創建在二叉樹中序遍歷的基礎上,
    2. 在遍歷中附設一指針 \(pre\) 指向樹中當前訪問結點的中序直接前驅,
    3. 每訪問一個結點就比較前驅結點 \(pre\) 和此結點是否有序
    4. 若遍歷結束後各結點和其中序直接前驅均知足有序,則此二叉樹即爲二叉排序樹,不然不是二叉排序樹
typedef int datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

// 函數 bisorttree()用於判斷二叉樹 t 是否爲二叉排序樹,
// 初始時 pre=NULL;flag=1;
// 結束時若 flag==1,則此二叉樹爲二叉排序樹,不然此二叉樹不是二叉排序樹。
void bisorttree(bintree t, bintree *pre, int *flag) {
    if (t && *flag == 1) {
        bisorttree(t->lchild, pre, flag); // 判斷左子樹
        // 訪問中序序列的第一個結點時不須要比較
        if (pre == NULL) {
            *flag = 1;
            *pre = t;
        } else { // 比較 t 與中序直接前驅 pre 的大小(假定無相同關鍵字)
            if ((*pre)->data < t->data) {
                *flag = 1;
                *pre = t;
            } else *flag = 0; //  pre 與 t 無序
        }
        bisorttree(t->rchild, pre, flag); // 判斷右子樹
    }
}

10、錯題集

  1. 設順序存儲的線性表共有 \(123\) 個元素,按分塊查找的要求等分紅 \(3\) 塊。若對索引表採用順序查找來肯定塊,並在肯定的塊中進行順序查找(尋找肯定的塊還須要 \(2\) 步),則在查找機率相等的狀況下,分塊查找成功時的平均查找長度爲 \(23\)

  2. 有數據 \({53,30,37,12,45,24,96}\) ,從空二叉樹開始逐步插入數據造成二叉排序樹,若但願高度最小,則應該選擇下列的序列輸入,答案 \(37,24,12,30,53,45,96\)

    1. 要建立一顆高度最小的二叉排序樹,就必須讓左右子樹的結點個數越接近越好
      1. 因爲給定的是一個關鍵字有序序列 \(a[start\ldots{end}]\),讓其中間位置的關鍵字 \(a[mid]\) 做爲根結點
      2. 左序列 \(a[start\dots{mid-1}]\)
        構造左子樹
      3. 右序列 \(a[mid+1\ldots{end}]\) 構造右子樹
  3. 若在 \(9\) 階 B-樹中插入關鍵字引發結點分裂,則該結點在插入前含有的關鍵字個數爲 \(8\)

    1. 注:若是是 \(m\) 階,答案是 \(m-1\)
  4. 下列敘述中,不符合 \(m\) 階B樹定義要求的是葉結點之間經過指針連接

  5. 在分塊檢索中,對 \(256\) 個元素的線性表分紅多少 \(16\) 塊最好。每塊的最佳長度(平均查找長度)\(17\),若每塊

    的長度爲 \(8\),其平均檢索的長度在順序檢索時爲 \(21\),在二分檢索時爲 \(9\)

    1. 假設線性表中共有 $n$ 個元素,且被均分紅 $b$ 塊,則每塊中的元素個數 $s=n/b$,待查元素在索引表中的平均查找長度爲 $E_1$,塊內查找時所需的平均查找長度爲 $E_b$
     2. 在順序檢索來肯定塊時,分塊查找成功時的平均查找長度爲 $ASL_{ids}=E_1+E_b=\frac{b+1}{2}+\frac{s+1}{2}=\frac{n/s+s}{2}+1=\frac{n+s^2}{2s}+1$
      	1. 當 $s=\sqrt{n}$ 時,$ASL_{ids}$ 取最小值 $\sqrt{n}+1$
    1. 在二分檢索來肯定塊時,分塊查找成功時的平均查找長度爲 \(ASL'_{ids}=E_1+E_b\approx{log_2(b+1)}-1+\frac{s+1}{2}\approx{log_2(\frac{n}{s}+1)+\frac{s}{2}}\)
  6. 設有關鍵碼 A、B、C 和 D,按照不一樣的輸入順序,共可能組成 \(14\) 種不一樣的二叉排序樹

    1. 使用公式:不一樣關鍵碼構造出不一樣的二叉排序樹的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)
  7. 含有 \(12\) 個結點的平衡二叉樹的最大深度是多少 \(4\)(設根結點深度爲 \(0\))、\(5\)(設根結點深度爲 \(1\)),並畫出一棵這樣的樹

    1. 假設 \(N_h\) 表示深度爲 \(h\) 的平衡二叉樹中含有的最少的結點數目
    2. 那麼,\(N_0=0\)\(N_1=1\)\(N_2=2\),而且 \(N_h=N_{h-1}+N_{h-2}+1\),根據平衡二叉樹平衡二叉樹的這一性質,\(N_5=12\),若是根結點深度爲 \(0\),則最大深度爲 \(4\)
    3. 圖習題9-6:
相關文章
相關標籤/搜索