查找效率最高即平均查找長度最小,根據前面所學知識,我們可以給出有序表在非等概率情況下應遵循的兩個原則:???
1、最先訪問的結點應是訪問概率最大的結點;?
2、每次訪問應使結點兩邊尚未訪問的結點的被訪概率之和盡可能相等。
這兩個原則可用一句話來表示,即判定樹為帶權內路徑長度之和最小的二叉樹,亦即:PH = ∑wihi? 最小,其中 n 為有序表長度,hi?為第 i 個結點在判定樹上的層次數,wi?= cpi,c 為某個常數,pi?為第 i 個結點的查找概率。
??????? 這樣的樹稱為靜態最優查找樹(static optimal search tree),構造這樣一棵樹的時間代價太大,亦即時間復雜度很大,因此我們通常是構造次優查找樹(nearly optimal search tree),構造它的時間代價遠遠低于構造最優查找樹,但查找性能只比最優查找樹差1%~2%,很少差3%以上。
次優查找樹的構造:?
??
??????? 設有序表每個記錄的權值為 wl,wl+1,…,wh,第一個應訪問的結點號為 i ,則有:?
Δpi?=?? ∑wj?- ∑wj?? 最小,即 Δpi?= Min {Δpj?}?
再分別對 {rl,rl+1,…,ri-1} 和 {ri+1,ri+2,…,rh} 分別構造次優查找樹。?
??
為便于計算,引入累計權值swi=∑wj,并設wl-1=swl-1=0,則:

????
??????? 由于在構造次優查找樹時沒有考慮前面說的原則一,因此被選為根的結點的權值可能比其鄰近結點的權值小,此時應對查找樹作適當的調整,將相鄰權值大的結點作為根結點。
????????? 次優查找樹的查找方法與折半查找類似,其平均查找長度與 log n 成正比。
注意:利用上述算法構造好次優二叉樹之后,可能并不是最優的,因為在構造過程中,沒有考慮單個關鍵字的相應權值,則有可能出現被選為根的關鍵字的權值比與
它相鄰的關鍵字的權值小。此時應做適當的調整:選取鄰近的權值較大的關鍵字作為次優查找樹的根節點(也就是左旋和右旋子樹
#include<iostream>
#include<cstring> #include<cstdio> #include<algorithm> #include<string> #include<cmath> #define N 100 #define MAXN 0x3f3f3f3f using namespace std;template<typename T> class TreeNode{public:TreeNode* child[2];T val;int w; TreeNode(){child[0] = child[1] = NULL;} };template<typename T> class NearlyOptimalSearchTree{//次優查找樹 public:int n;T val[N];int w[N];int sw[N];TreeNode<T> *t;void input();void init();void outT(TreeNode<T>* t);private:void buildT(int ld, int rd, TreeNode<T>* &t);//建立次優查找樹 void adjustment(TreeNode<T>* &t);//調整次優查找樹 void rotateT(TreeNode<T>* &t, int x); };template<typename T> void NearlyOptimalSearchTree<T>::input(){cin>>n;for(int i=1; i<=n; ++i)cin>>val[i]>>w[i]; }template<typename T> void NearlyOptimalSearchTree<T>::init(){sw[0] = 0;for(int i=1; i<=n; ++i) sw[i] = sw[i-1]+w[i];buildT(1, n, t);cout<<"沒有調整前的先序遍歷:"<<endl;outT(t);adjustment(t);cout<<endl<<"調整后的先序遍歷:"<<endl;outT(t);cout<<endl; }template<typename T> void NearlyOptimalSearchTree<T>::buildT(int ld, int rd, TreeNode<T>* &t){if(ld > rd) return;int minN = MAXN;int i;for(int j=ld; j<=rd; ++j){int ans = sw[rd] - sw[j-1] - sw[j]; ans = abs(ans);if(minN > ans){minN = ans;i = j;}}t = new TreeNode<T>;t->val = val[i];t->w = w[i];if(ld==rd) return;buildT(ld, i-1, t->child[0]);buildT(i+1, rd, t->child[1]); }template<typename T> void NearlyOptimalSearchTree<T>::adjustment(TreeNode<T>* &t){if(!t) return;int lmax = 0, rmax = 0;if(t->child[0]) lmax = t->child[0]->w;if(t->child[1]) rmax = t->child[1]->w;int maxVal = max(lmax, rmax);if(t->w < maxVal){if(maxVal == lmax){rotateT(t, 1);//右旋子樹 } else {rotateT(t, 0);//左旋子樹 } }adjustment(t->child[0]);adjustment(t->child[1]); }template<typename T> void NearlyOptimalSearchTree<T>::rotateT(TreeNode<T>* &o, int x){TreeNode<T>* k = o->child[x^1];o->child[x^1] = k->child[x];k->child[x] = o;o = k; }template<typename T> void NearlyOptimalSearchTree<T>::outT(TreeNode<T>* t){if(!t) return;cout<<t->val<<" ";outT(t->child[0]);outT(t->child[1]); }int main(){NearlyOptimalSearchTree<string> nost;nost.input();nost.init();return 0; }
/*
演示結果如下:
9
A 1
B 1
C 2
D 5
E 3
F 4
G 4
H 3
I 5
沒有調整前的先序遍歷:
F D B A C E G H I
調整后的先序遍歷:
D C B A F E G I H
5
A 1
B 30
C 2
D 29
E 2
沒有調整前的先序遍歷:
C B A D E
調整后的先序遍歷:
B A D C E
*/
?
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<string> #include<iomanip> #include<cmath> #include<queue> #define N 100 #define MAXN 0x3f3f3f3f using namespace std;template<typename T> class TreeNode{public:TreeNode* child[2];T val;int w; int d;//距離屏幕左端的寬度 TreeNode(){child[0] = child[1] = NULL;} };template<typename T> class NearlyOptimalSearchTree{//次優查找樹 public:int n;T val[N];int w[N];int sw[N];TreeNode<T> *t;void input();void init();void outT(TreeNode<T>* t);private:int width;int step;void buildT(int ld, int rd, TreeNode<T>* &t);//建立次優查找樹 void adjustment(TreeNode<T>* &t);//調整次優查找樹 void rotateT(TreeNode<T>* &t, int x);void widthT(TreeNode<T>* t);//計算每個節點到屏幕左端的距離 };template<typename T> void NearlyOptimalSearchTree<T>::input(){cin>>n;for(int i=1; i<=n; ++i)cin>>val[i]>>w[i]; }template<typename T> void NearlyOptimalSearchTree<T>::init(){sw[0] = 0;width = 0;step = 2;for(int i=1; i<=n; ++i) sw[i] = sw[i-1]+w[i];buildT(1, n, t);cout<<"沒有調整前的先序遍歷:"<<endl;outT(t);adjustment(t);cout<<endl<<"調整后的先序遍歷:"<<endl;outT(t);cout<<endl; }template<typename T> void NearlyOptimalSearchTree<T>::buildT(int ld, int rd, TreeNode<T>* &t){if(ld > rd) return;int minN = MAXN;int i;for(int j=ld; j<=rd; ++j){int ans = sw[rd] - sw[j-1] - sw[j]; ans = abs(ans);if(minN > ans){minN = ans;i = j;}}t = new TreeNode<T>;t->val = val[i];t->w = w[i];if(ld==rd) return;buildT(ld, i-1, t->child[0]);buildT(i+1, rd, t->child[1]); }template<typename T> void NearlyOptimalSearchTree<T>::adjustment(TreeNode<T>* &t){if(!t) return;int lmax = 0, rmax = 0;if(t->child[0]) lmax = t->child[0]->w;if(t->child[1]) rmax = t->child[1]->w;int maxVal = max(lmax, rmax);if(t->w < maxVal){if(maxVal == lmax){rotateT(t, 1);//右旋子樹 } else {rotateT(t, 0);//左旋子樹 } }adjustment(t->child[0]);adjustment(t->child[1]); }template<typename T> void NearlyOptimalSearchTree<T>::rotateT(TreeNode<T>* &o, int x){TreeNode<T>* k = o->child[x^1];o->child[x^1] = k->child[x];k->child[x] = o;o = k; }template<typename T> void NearlyOptimalSearchTree<T>::widthT(TreeNode<T>* t){if(!t) return;widthT(t->child[0]);t->d = width;width+=step; widthT(t->child[1]); }template<typename T> void NearlyOptimalSearchTree<T>::outT(TreeNode<T>* t){width=0;widthT(t);queue<TreeNode<T>*> q, qq;q.push(t);int n=1;//當前層的節點個數 int i=1;//當前層第幾個節點 int nn=0;//統計下一次的節點的個數int pred;//前一個節點距離左屏幕的距離 while(!q.empty()){TreeNode<T>* tt = q.front();q.pop();qq.push(tt);if(tt != t){//不是根節點, 打印分枝豎線 if(i==1){printf("%*s", tt->d, "|");pred = tt->d;} else {printf("%*s", tt->d-pred, "|");pred = tt->d;}}//放入孩子節點 if(tt->child[0]) q.push(tt->child[0]), ++nn;if(tt->child[1]) q.push(tt->child[1]), ++nn;++i; if(i>n){//上一層訪問完畢 i=1;n = nn;nn = 0;printf("\n");bool first = true;//是否是這一行的第一個節點 int ld, rd; while(!qq.empty()){//打印上層節點字符 TreeNode<T>* tt = qq.front();qq.pop();if(first){cout<<setw(tt->d)<<tt->val;pred = tt->d;ld = tt->d;if(tt->child[0])ld = tt->child[0]->d; } else {cout<<setw(tt->d - pred)<<tt->val;pred = tt->d;}first = false;if(qq.empty()){//這一層的最后一個節點 rd = tt->d+1;if(tt->child[1])rd = tt->child[1]->d;}}printf("\n");if(q.empty()) break;//這是最后一層 cout<<setw(ld-1)<<"";for(int i=ld; i<=rd; ++i)printf("-") ;printf("\n");}} }int main(){NearlyOptimalSearchTree<string> nost;nost.input();nost.init();return 0; }
/*
//演示結果
9
A 1
B 1
C 2
D 5
E 3
F 4
G 4
H 3
I 5
沒有調整前的先序遍歷:
F
-------
| |
D G
-------------
| | |
B E H
-----------------
| | |
A C I
調整后的先序遍歷:
D
-------
| |
C F
-----------
| | |
B E G
-----------------
| |
A I
------------------
|
H
*/
?