【动态规划】最优二叉搜索树(C++)

一、关于最优二叉搜索树

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……
·

三、友情链接~


最后,非常欢迎大家来讨论指正哦!

  • 3
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值