第9章 檢索


第9章 檢索

數據結構與算法_師大完整教程目錄(更有python、go、pytorch、tensorflow、爬蟲、人工智能教學等着你):https://www.cnblogs.com/nickchen121/p/13768298.html

一、檢索的基本概念

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

二、線性表的檢索

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. 失敗之后,判斷數據元素和中間結點的大小

如果中間結點大於數據元素,則在前半部分繼續二分檢索,\(right\) 變成 \(mid-1\)\(mid-1\)是因為 \(mid\) 已經做出過判斷,不需要再次比較)
如果中間結點小於數據元素,則在后半部分繼續二分檢索,\(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\)

\(s=\sqrt{n}\) 時,\(ASL_{ids}\) 取最小值 \(\sqrt{n}+1\) (最佳查找長度)
3. 在二分檢索來確定塊時,分塊查找成功時的平均查找長度為 \(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. 建立索引表(數組):

索引表結點中存儲兩個元素:一個元素表示某一塊在原數組中的開始下標;另一個元素表示某一塊中最大的值
3. 讓被查找值和最大的值進行比較,獲取查找值在數組中比較的下標范圍
4. 最后在該范圍內進行順序查找即可
4. 圖分塊檢索:

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

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

三、二叉排序樹

  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. 圖生成二叉排序樹:

四、豐滿樹和平衡樹

  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\) 型 平衡旋轉:

    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型平衡旋轉

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型\) 平衡旋轉):

    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. 算法步驟:

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

五、二叉排序樹和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. 重復步驟 \(2、3\),直到 \(F\) 中只含有一棵樹為止

5.3.1 構造Huffman樹

  1. 對於結點序列 \(6、10、16、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\)

六、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)\)

其中 \(k_i(1\leq{i}\leq{n})\) 為關鍵字,且 \(k_i<k_i+1(1\leq{i}\leq{n})\)
\(p_j(0\leq{j}\leq{n})\) 為指向子樹根結點的指針,且 \(p_j(0\leq{j}<n)\) 所指子樹中所有結點的關鍵字均小於 \(k_j+1\)
\(p_n\) 所指子樹中所有結點的關鍵字均大於 \(k_n\)
\(n(\lceil{m/2}\rceil-1\leq{n}\leq{m-1})\) 為關鍵字的個數(\(n+1\) 為子樹個數)
8. 除根結點之外所有非終端結點至少有 棵子樹,也即每個非根結點至少應有 \(\lceil{m/2}\rceil-1\) 個關鍵字
9. 所有的葉子結點都出現在同一層上,並且不帶信息(可以看作是外部結點或查找失敗的結點,實際上這些結點不存在,指向這些結點的指針為空)
2. 圖3階B-樹:

6.2 B-樹的基本操作

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

6.3 B+樹(大綱未規定)

七、散列表檢索

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\) 為每次再探測時的地址增量

線性探測再散列:\(d_i = 1,2,3,\cdots,m-1\)
二次探測再散列:\(d_i=1^2,{-1}^2,2^2,{-2}^2,\cdots,k^2,{-k}^2(k\leq{m/2})\)
隨機探測再散列:\(d_i = 隨機數序列\)
5. 聚集:幾個 \(Hash\) 地址不同的關鍵字爭奪同一個后繼 \(Hash\) 地址的現象
6. 開放定址法容易發生聚集現象,尤其是采用線性探測再散列
7. 圖開放定址法:
2. 再哈希法:某個元素 \(k\) 在原散列函數 \(H(k)\) 的映射下與其他數據發生碰撞時,采用另外一個 \(Hash\) 函數 \(H_i(k)\) 計算 \(k\) 的存儲地址

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

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

    1. | | 開放定址法 | 再哈希法 | 拉鏈法 |
      | :--: | :--: | :--: | :--: |
      | 優點 | 不需要額外的計算時間和空間 | 不易 “聚集” | 無 “聚集”;非同義詞不會沖突 |
      | 缺點 | 容易發生 “聚集” 現象 | 增加了計算時間 | 需要額外的空間 |

7.4 散列表檢索的應用

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

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

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

九、算法設計題

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); // 判斷右子樹
    }
}

十、錯題集

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

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

    1. 要創建一顆高度最小的二叉排序樹,就必須讓左右子樹的結點個數越接近越好

由於給定的是一個關鍵字有序序列 \(a[start\ldots{end}]\),讓其中間位置的關鍵字 \(a[mid]\) 作為根結點
左序列 \(a[start\dots{mid-1}]\)
構造左子樹
右序列 \(a[mid+1\ldots{end}]\) 構造右子樹
3. 若在 \(9\) 階 B-樹中插入關鍵字引起結點分裂,則該結點在插入前含有的關鍵字個數為 \(8\)

1. 注:如果是 $m$ 階,答案是 $m-1$
  1. 下列敘述中,不符合 \(m\) 階B樹定義要求的是葉結點之間通過指針鏈接
  2. 在分塊檢索中,對 \(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}}$
  1. 設有關鍵碼 A、B、C 和 D,按照不同的輸入順序,共可能組成 \(14\) 種不同的二叉排序樹

    1. 使用公式:不同關鍵碼構造出不同的二叉排序樹的可能性有 \(\frac{1}{n+1}C_{2n}^{n}\)
  2. 含有 \(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:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM