首先看下关于最优二叉查找树的描述
给定一个由n个互异的关键字组成的序列K={k1,k2,…,kn},且关键字有序,对于每一个关键字ki,一次搜索为ki的概率是pi。某些搜索的值可能不在K内,因此还有n+1个虚拟键d0,d1,…,dn代表不再K内的值。d0代表所有小于k1的值,dn代表所有大于kn的值,对于i=1,2,…,n-1,di代表所有位于ki和ki+1之间的值。对每个虚拟键di,一次搜索对应于di的概率是qi。定义在T内一次搜索的期望代价为E=∑(depth(ki)+1)*pi+∑(depth(di)+1)*qi=1+∑depth(ki)*pi+∑depth(di)*qi
要找的最小的E则为最优二叉查找树
首先,我们定义
w[i][j] = q[i-1]+p[i]+q[i]+p[i+1] + …. +q[j]+p[j], 显然w[i][j] = w[i][j-1] + q[j]+p[j]
E[i][j]为由结点i到j构成的最优二叉查找树的期望代价,如果我们假设它的根为r,那么,显然,他的左子树(由结点i,i+1,…r-1构成)也是一棵最优二叉查找树,右子树也是一棵最优二叉查找树,否则,我们可以调整他的子树,便得到一棵比原先更优的二叉查找树,与前提矛盾,所以,最优二叉查找树具有最优子结构
单单就左子树的查找代价为E[i][r-1]
单单就右子树的查找代价为E[r+1][j]
而将左子树和右子树作为子树连接到一个根r上面,显然,整体的查找代价增加了p[r] +( q[i-1]+p[i]+…q[r-1] )[注:左子树增加的,因为所有的实结点和虚节点均多了一层]+ (q[r+1]+p[r+1]+…q[j])[注:右子树增加的代价]
增加的代价其实转换一下就是w[i][j]
那么,E[i][j] = E[i][r-1] + E[r+1][j] + W[i][j] (r=i, …j)
要使得E[i][j]最小,就要选取合适的r用公式表达即为
E[i][j] = min{E[i][r-1] + E[r+1][j] + W[i][j] }(r=i, …j)
注意E[i][i-1]定义为q[i-1],同理E[j+1][j]为q[j]原因是因为r=i,或j的时候虽然左或右子树虽然为空,但是依旧包括虚节点d[i-1]以及d[j],如果想不通的话可以通过E[i][i]反推E[i][i-1]
反推如下
E[i][i] = E[i][i-1] + E[i+1][i] + W[i][i] //相当于循环中令r=i
左边 = q[i] + 2*(q[i-1]+q[i]) //定义
右边w[i][i] = q[i-1]+p[i]+q[i]
所以得到E[i][i-1]+E[i+1][i] = q[i-1]+q[i]
有了上面的思路,我们就可以从一个结点出发,慢慢地扩充,每一步的扩充都基于之前更少结点的W和E,这种扩充方式有点类似于矩阵连乘问题的最小乘法次数
#include <iostream>
#include <limits>
#define N 5
using namespace std;
void BST(double q[], double p[], double w[][N+1], double e[][N+1], int root[][N+1]) {
//将w[i][i-1], e[i][i-1]初始化
for (int i = 1; i <= N+1; i++) {
w[i][i-1] = q[i-1]; //保证在求w[i][i]的时候q[i-1]+p[i]+q[i]=w[i][i] = w[i][i-1]+p[i]+q[i]
e[i][i-1] = q[i-1]; //当r选择i或j作为根的时候,虽然左子树或右子树不包含关键字,但是依然包含虚拟键di-1和dj
//对应到这边就是e[i][i-1]=q[i-1]以及e[j+1][j] = q[j];可以自己模拟试试就可以很轻松地发现这个规律
}
for (int num = 1; num <= N; num++) {// d表示当前循环求几个结点构成的二叉树的最小代价
for (int i = 1; i <= N-num+1; i++) {
int j = i+num-1; // 表示当前有num个结点参与
//搜索最小的e[i][j]
e[i][j] = INT_MAX;
w[i][j] = w[i][j-1] + p[j] + q[j];
for (int r = i; r <= j; r++) {
double tmp = e[i][r-1] + e[r+1][j] + w[i][j];
if (tmp < e[i][j]) {
e[i][j] = tmp;
root[i][j] = r;
}
}
}
}
}
int main() {
double p[N+1] = {0.00,0.15,0.10,0.05,0.10,0.20};
double q[N+1] = {0.05,0.10,0.05,0.05,0.05,0.10};
int root[N+1][N+1];
double w[N+1][N+1];
double e[N+1][N+1];
BST(q, p, w, e, root);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
cout << root[i][j] << " ";
}
cout << endl;
}
return 0;
}