前面章節所介紹的有關在靜態查找表中對特定關鍵字進行順序查找、折半查找或者分塊查找,都是在查找表中各關鍵字被查找概率相同的前提下進行的。
而在某些情況下,查找表中各關鍵字被查找的概率是不同的。例如水果商店中有很多種水果,對於不同的顧客來說,由於口味不同,各種水果可能被選擇的概率是不同的。假設該顧客喜吃酸,那么相對於蘋果和橘子,選擇橘子的概率肯定要更高一些。例如查找表中有 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 表示)最小時,查找性能最優,稱該二叉樹為靜態最優查找樹。
但是由於構造最優查找樹花費的時間代價較高,而且有一種構造方式創建的判定樹的查找性能同最優查找樹僅差 1% - 2%,稱這種極度接近於最優查找樹的二叉樹為次優查找樹。帶權路徑之和的計算公式為:PH = 所有結點所在的層次數 * 每個結點對應的概率值。
次優查找樹的構建方法
首先取出查找表中每個關鍵字及其對應的權值,采用如下公式計算出每個關鍵字對應的一個值:
其中 w
j 表示每個關鍵字的權值(被查找到的概率),h 表示關鍵字的個數。
表中有多少關鍵字,就會有多少個 △P
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,以 F 為分界線,左側子表為 F 結點的左子樹,右側子表為 F 結點的右子樹(如上圖右側所示),繼續查找左右子樹的根結點:例如,關鍵字 F 的 △P 的計算方式為:從 G 到 I 的權值和 - 從 A 到 E 的權值和 = 4+3+5-1-1-2-5-3 = 0。


后邊還有一步,判斷關鍵字 A 和 C 在樹中的位置,最后一步兩個關鍵字的權值為 0 ,分別作為結點 B 的左孩子和右孩子,這里不再用圖表示。
注意:在建立次優查找樹的過程中,由於只根據的各關鍵字的 P 的值進行構建,沒有考慮單個關鍵字的相應權值的大小,有時會出現根結點的權值比孩子結點的權值還小,此時就需要適當調整兩者的位置。
總結
由於使用次優查找樹和最優查找樹的性能差距很小,構造次優查找樹的算法的時間復雜度為O(nlogn)
,因此可以使用次優查找樹表示概率不等的查找表對應的靜態查找表(又稱為靜態樹表)。