一、关于最优二叉搜索树
1. 问题描述:
- 给定一个由n个互异的关键字组成的有序序列K={k1<k2<k3<,……,<kn}和它们被查询的概率P={p1,p2,p3,……,pn},要求构造一棵二叉查找树T,使得查询所有元素的总代价最小。
- To be continue……
2. 问题分析:
- 二叉搜索树的构造:
- 对于一个搜索树,当搜索的元素在树内时,表示搜索成功;当不在树内时,表示搜索失败。
- 用一个“虚叶子节点(虚拟键)”来表示搜索失败的情况,因此需要n+1个虚叶子节点(虚拟键){d0<d1<……<dn},对应di的概率序列是Q={q0,q1,……,qn}。
- 其中d0表示搜索元素小于k1的失败结果,dn表示搜索元素大于kn的失败情况。di(0<i<n)表示搜索节点在ki和k(i+1)之间时的失败情况。
- To be continue……
二、算法实现
1. 动态规划 - 自底向上
// 最优二叉搜索树 自底向上非递归的动态规划算法
#include<iostream>
#include<cstdio>
using namespace std;
void OptimalBinarySearchTree(int n, double* p, double* q, double** root, double** w, double** e);
void printBintree(double** root, int i, int j);
int main()
{
cout << "最优二叉搜索树 自底向上非递归的动态规划算法\n\n";
int n; // 根节点数
double* p; // 查找 关键字 的概率
double* q; // 查找 虚拟键 的概率
double** root; // 根节点
double** w; // 子树概率总和
double** e; // 子树期望
cout << "请输入节点数目 n:";
cin >> n;
p = new double[n + 1];
q = new double[n + 1];
root = new double* [n + 2];
w = new double* [n + 2];
e = new double* [n + 2];
for (int i = 0;i < n + 2;i++)
{
root[i] = new double[n + 1];
w[i] = new double[n + 1];
e[i] = new double[n + 1];
//memset(w[i], 0, sizeof(double) * (n + 1));
//memset(e[i], 0, sizeof(double) * (n + 1));
}
cout << "请输入节点查找成功的概率(n个):";
for (int i = 1;i <= n;i++)
cin >> p[i];
cout << "请输入节点查找失败的概率(n+1个):";
for (int i = 0;i <= n;i++)
cin >> q[i];
// 构造最优二叉搜索树
OptimalBinarySearchTree(n, p, q, root, w, e);
// 输出二叉树
printBintree(root, 1, n);
for (int i = 0;i < n + 2;i++)
{
for (int j = 0;j < n + 1;j++)
{
cout << e[i][j] << "\t";
}
cout << endl;
}
// 删除指针
delete[] p;
delete[] q;
for (int i = 0;i < n + 2;i++)
{
delete[] root[i];
delete[] w[i];
delete[] e[i];
}
delete[] root;
delete[] w;
delete[] e;
}
void OptimalBinarySearchTree(int n, double* p, double* q, double** root, double** w, double** e)
{
// 处理w[i,j]和e[i,j]中i=j+1的情况,这种情况都是q[i-1]
for (int i = 1; i <= n + 1; i++) {
w[i][i - 1] = q[i - 1];
e[i][i - 1] = 0;
}
int i = 0; // 子问题起始节点的下标
int j = 0; // 子问题最后节点的下标
int r = 0; // 子问题根节点的下标
double temp = 0; // 存放计算得到的临时期望
// 子问题中节点数量(ki~kj的长度),从0到n-1
for (int len = 0; len < n; len++)
{
// 循环所有节点数为len的子问题
for (i = 1; i <= n - len; i++)
{
j = len + i;
w[i][j] = w[i][j - 1] + p[j] + q[j];
// 当第一个节点为根节点时的期望
e[i][j] = e[i + 1][j]; // + w[i][j]
root[i][j] = i;
// 当第i+1个节点为根节点时的期望
for (r = i + 1; r <= j; r++)
{
temp = e[i][r - 1] + e[r + 1][j]; // + w[i][j]
// 保存最优解
if (temp < e[i][j])
{
e[i][j] = temp;
root[i][j] = r;
}
}
// 如果将上面的注释“+ w[i][j]”写入对应表达式,可删此句
e[i][j] += w[i][j];
}
}
}
void printBintree(double** root, int i, int j)
{
if (i < j)
{
int r = root[i][j];
cout << "S" << r << "是根\n";
if (root[i][r - 1] > 0)
cout << "S" << r << "的左孩子是S" << root[i][r - 1] << endl;
if (root[r + 1][j] > 0)
cout << "S" << r << "的右孩子是S" << root[r + 1][j] << endl;
printBintree(root, i, r - 1);
printBintree(root, r + 1, j);
}
}
2. 运行结果展示
3. 算法改进
此算法还一个改进版…
To be continue……
·
三、友情链接~
- 其它一些常见算法请参阅此链接~
最后,非常欢迎大家来讨论指正哦!