查找效率最高即平均查找长度最小,根据前面所学知识,我们可以给出有序表在非等概率情况下应遵循的两个原则:
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
*/