1. 程式人生 > >查詢演算法 | 靜態樹表(次優查詢樹)詳細分析

查詢演算法 | 靜態樹表(次優查詢樹)詳細分析

前面章節所介紹的有關在靜態查詢表中對特定關鍵字進行順序查詢、折半查詢或者分塊查詢,都是在查詢表中各關鍵字被查詢概率相同的前提下進行的。

例如查詢表中有 n 個關鍵字,表中每個關鍵字被查詢的概率都是 1/n。在等概率的情況,使用折半查詢演算法的效能最優。

而在某些情況下,查詢表中各關鍵字被查詢的概率是不同的。例如水果商店中有很多種水果,對於不同的顧客來說,由於口味不同,各種水果可能被選擇的概率是不同的。假設該顧客喜吃酸,那麼相對於蘋果和橘子,選擇橘子的概率肯定要更高一些。

在查詢表中各關鍵字查詢概率不相同的情況下,對於使用折半查詢演算法,按照之前的方式進行,其查詢的效率並不一定是最優的。例如,某查詢表中有 5 個關鍵字,各關鍵字被查詢到的概率分別為:0.1,0.2,0.1,0.4,0.2(全部關鍵字被查詢概率和為 1 ),則根據之前介紹的折半查詢演算法,建立相應的判定樹為(樹中各關鍵字用概率表示):

圖 1 折半查詢對應的判定樹

折半查詢查詢成功時的平均查詢長度的計算方式為:

ASL = 判定樹中各結點的查詢概率*所在層次

所以該平均查詢長度為:

ASL=0.1*1 + 0.1*2 + 0.4*2 + 0.2*3 + 0.2*3 = 2.3

由於各關鍵字被查詢的概率是不相同的,所以若在查詢時遵循被查詢關鍵字先和查詢概率大的關鍵字進行比對,建立的判定樹為:

圖 2 折半查詢對應的新判定樹

相應的平均查詢長度為:

ASL=0.4*1 + 0.2*2 + 0.2*2 + 0.1*3 + 0.1*3=1.8

後者折半查詢的效率要比前者高,所以在查詢表中各關鍵字查詢概率不同時,要考慮建立一棵查詢效能最佳的判定樹。若在只考慮查詢成功的情況下,描述查詢過程的判定樹其帶權路徑長度之和(用 PH 表示)最小時,查詢效能最優,稱該二叉樹為靜態最優查詢樹

帶權路徑之和的計算公式為:PH = 所有結點所在的層次數 * 每個結點對應的概率值。

但是由於構造最優查詢樹花費的時間代價較高,而且有一種構造方式建立的判定樹的查詢效能同最優查詢樹僅差 1% - 2%,稱這種極度接近於最優查詢樹的二叉樹為次優查詢樹

次優查詢樹的構建方法


次優查詢樹的演算法描述如下:

已知一個序列:(rl,rl+1,……,rh),遞增有序。它對應的權值為:(wl,wl+1,……,wh)。

定義:
Δ P

i = j = i + 1 h w j j = l i 1 w j \Delta P_i = \begin{vmatrix} \sum_{j=i+1}^{h} w_j - \sum_{j=l}^{i-1} w_j \end{vmatrix}

取 △Pi 最小的那個元素 i 作為根,然後分別對子序列(rl,rl+1,……,ri-1)和(ri+1,ri+2,……,rh)同樣構造次優查詢樹,並分別作為 i 的左子樹和右子樹。

在計算 △Pi 時,實際上就是計算元素 i 前面的元素的權值之和與元素i後面的元素的權值之和的差值。如果對每一個元素都要這樣計算就有很多重複計算,為了提高效率,我們引入“累計權值和”:
s w i = j = l i w j sw_i = \sum_{j=l}^{i} w_j

並設 wl-1 = 0 和 swl-1 = 0,則
{ s w i 1 s w l 1 = j = l i 1 w j s w h s w i = j = i + 1 h w j \begin{cases} sw_{i-1} - sw_{l-1} = \sum_{j=l}^{i-1} w_j \\ sw_h - sw_i = \sum_{j=i+1}^{h} w_j \end{cases}

Δ P i = ( s w h s w i ) ( s w i 1 s w l 1 ) = ( s w h + s w l 1 ) s w i s w i 1 \begin{matrix} \Delta P_i = \begin{vmatrix} (sw_h - sw_i) - (sw_{i-1} - sw_{l-1}) \end{vmatrix} \\ = \begin{vmatrix} (sw_h + sw_{l-1}) - sw_i - sw_{i-1} \end{vmatrix} \end{matrix}

從上面這個公式可以看出,我們只要一次性地求出所有元素的 swi 值並儲存起來,以後每次求 △Pi 就只要查表中對應的四個 sw 值進行計算就可以了。

我們先來看看下面這個例子:

關鍵字 A B C D E F G H I
權值 0 1 1 2 5 3 4 4 3 5
j 0 1 2 3 4 5 6 7 8 9
swj 0 1 2 4 9 12 16 20 23 28
△Pj 27 25 22 15 7 0 8 15 23
↑i
△Pj 11 9 6 1 9 8 1 7
↑i ↑i
△Pj 3 1 2 0 0 0
↑i ↑i ↑i ↑i
△Pj 0 0
(根) ↑i ↑i

最終構造的次優二叉樹如下圖:

程式碼實現為:

typedef int KeyType;//定義關鍵字型別
typedef struct{
    KeyType key;
}ElemType;//定義元素型別
typedef struct BiTNode{
    ElemType data;
    struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

//定義變數
int i;
int min;
int dw;
//建立次優查詢樹,R陣列為查詢表,sw陣列為儲存的各關鍵字的概率(權值),low和high表示的sw陣列中的權值的範圍
void SecondOptimal(BiTree T, ElemType R[], float sw[], int low, int high){
    //由有序表R[low...high]及其累計權值表sw(其中sw[0]==0)遞迴構造次優查詢樹
    i = low;
    min = abs(sw[high] - sw[low]);
    dw = sw[high] + sw[low - 1];
    //選擇最小的△Pi值
    for (int j = low+1; j <=high; j++){
        if (abs(dw-sw[j]-sw[j-1])<min){
            i = j;
            min = abs(dw - sw[j] - sw[j - 1]);
        }
    }
   
    T = (BiTree)malloc(sizeof(BiTNode));
    T->data = R[i];//生成結點(第一次生成根)
    if (i == low) T->lchild = NULL;//左子樹空
    else SecondOptimal(T->lchild, R, sw, low, i - 1);//構造左子樹
    if (i == high) T->rchild = NULL;//右子樹空
    else SecondOptimal(T->rchild, R, sw, i + 1, high);//構造右子樹
}

完整事例演示


例如,一含有 9 個關鍵字的查詢表及其相應權值如下表所示:

則構建次優查詢樹的過程如下:

首先求出查詢表中所有的 △P 的值,找出整棵查詢表的根結點:

例如,關鍵字 F 的 △P 的計算方式為:從 G 到 I 的權值和 - 從 A 到 E 的權值和 = 4+3+5-1-1-2-5-3 = 0。

通過上圖左側表格得知,根結點為 F,以 F 為分界線,左側子表為 F 結點的左子樹,右側子表為 F 結點的右子樹(如上圖右側所示),繼續查詢左右子樹的根結點:

通過重新分別計算左右兩查詢子表的 △P 的值,得知左子樹的根結點為 D,右子樹的根結點為 H (如上圖右側所示),以兩結點為分界線,繼續判斷兩根結點的左右子樹:

通過計算,構建的次優查詢樹如上圖右側二叉樹所示。

後邊還有一步,判斷關鍵字 A 和 C 在樹中的位置,最後一步兩個關鍵字的權值為 0 ,分別作為結點 B 的左孩子和右孩子,這裡不再用圖表示。

注意:在建立次優查詢樹的過程中,由於只根據的各關鍵字的 P 的值進行構建,沒有考慮單個關鍵字的相應權值的大小,有時會出現根結點的權值比孩子結點的權值還小,此時就需要適當調整兩者的位置。

總結


由於使用次優查詢樹和最優查詢樹的效能差距很小,構造次優查詢樹的演算法的時間複雜度為 O(nlogn),因此可以使用次優查詢樹表示概率不等的查詢表對應的靜態查詢表(又稱為靜態樹表)。