问题:给定一组单词w1,w2,...,wn和它们出现的固定概率p1,p2,...pn。问题是要以某种方法在一棵二叉查找树中安放这些单词使得总的期望访问时间最小。注意:二叉查找树中结点的值就是该单词出现的顺序。比如单词输入顺序:a(0.22) am(0.18) and(0.20) egg(0.05) if(0.25) the(0.02) two(0.08),括号里面数字是该单词出现的概率。单词插入二叉查找树要满足二叉查找树的性质,即左子树的值比结点的值小,右子树的结点值比结点值大。例如:单词and 为结点时,a am只能在左子树上。egg if the two只能在右子树上。
用动态规划实现:
假设把一些单词w(i), w(i+1), ...w(j-1),w(j)。放到一颗二叉查找树中,设最优二叉查找树以w(k)作为根结点,其中i=<k<=j。此时为了满足二叉查找树性质,左子树必须包含 w(i),...w(k-1),而右子树包含w(k+1),..w(j)。这两棵子树也都是最优的,否则总能最优子树代替它们。当i=1,j=n时就完成二叉查找树的构建。c[i][j]表示最优二叉查找树的开销,左子树的开销c[i][k-1],右子树的开销c[k+1][j]。这两棵子树的每个结点从wi开始都比从它们对应的根开始深一层,于是得到公式:
其中pt为第t个单词出现的概率。
递归算法是自底向上求解的,拿上面的例子讲述求解过程,a-am,c[1][2]=0.22+2*0.18=0.58,a。其中a-am表示a-am相邻单词构建二叉查找树,c[1][2]=0.58表示最小查找的开销时间,a表示以a为根结点构建二叉查找树。
(1) 求解1个单词构成二叉查找树最小开销(当然就自己,也就是其概率),即
c[1][1]=0.22;c[2][2]=0.18;c[3][3]=0.20;c[4][4]=0.05;c[5][5]=0.25;c[6][6]=0.02;c[7][7]=0.08。
(2)相邻2个单词构成二叉查找树最小开销:
a-am,c[1][2]=0.58,a;am-and,c[2][3]=0.56,and;and-egg,c[3][4]=0.3,and;egg-if,c[4][5]=0.35,if;if-the,c[5][6]=0.29,if;the-two,c[6][7]=0.12,two
(3)相邻3个单词构成二叉查找树最小开销:
a-and,c[1][3]=1.02,am;am-egg,c[2][4]=0.66,and;and-if,c[3][5]=0.80,if;egg-the,c[4][6]=0.39,if;if-two,c[5][7]=0.47,if
(4)相邻4个单词构成二叉查找树最小开销:
a-egg,c[1][4]=1.17,am;am-if,c[2][5]=1.21,and;and-the,c[3]6]=0.84,if;egg-two,c[4][7]=0.57,if
(5)相邻5个单词构成二叉查找树最小开销:
a-if,c[1][5]=1.83,and;am-the,c[2][6]=1.27,and;and-two,c[3][7]=1.02,if
(6)相邻6个单词构成二叉查找树最小开销:
a-the,c[1][6]=1.89,and;am-two,c[2][7]=1.53,and
(7)相邻7个单词构成二叉查找树最小开销:
a-two,c[1][7]=2.15,and
完成二叉查找树的构建,最小的开销为:2.15。
这样就完成了二叉查找树的整体构建,并把每个根结点都保存起来,程序中toBinary(int L,int R)根据保存的根结点信息,构建起来的二叉查找树。最后把该树以前序遍历输出。
dynamic.h
#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;
struct Node//保存每一个结点的信息
{
string name;//字符串
double p;//其出现的概率
Node* left;//指向该结点的左儿子
Node* right;//指向该结点的右儿子
Node(string S,double P,Node* L,Node* R):name(S),p(P),left(L),right(R){}
};
class Binary
{
public:
Binary();
void insert(string s,double pro);//把所有结点存储在一个N的vector中
void generate(); //寻找最佳的查询开销
void toBinary(int L,int R); //根据path中保存的路径生成查找二叉树
void show(); //输出最小的查找开销
void preprint(Node* t); //前序遍历查找二叉树,输出每个结点的字符
private:
vector<Node>N;//保存每个结点的信息
vector<vector<double> >c;//存储访问开销
vector<vector<int> >path;//保存根节点位置,比如例子中:a am and egg if the two,其最优的根结点在i=3处,将i=3保存在path[1][7]中
};
dynamic.cpp
#include "stdafx.h"
#include"dynamic.h"
#include<iostream>
#include<vector>
#include<string>
#include<limits>
Binary::Binary()
{
Node n(" ",0,NULL,NULL);
N.push_back(n);
}
void Binary::insert(string s,double p)
{
Node n(s,p,NULL,NULL);
N.push_back(n);
}
void Binary::generate()
{
int size=N.size();
c.resize(size+1);
path.resize(size);
for(int i=0;i<size+1;i++)
{
c[i].resize(size+1);
if(i<size)
path[i].resize(size);
}
for(int i=0;i<size+1;i++)//对c和path进行初始化进行初始化:有n个结点,N大小为(n+1)*(n+1);path大小(n+1)*(n+1);c大小(n+2)*(n+2)
for(int j=0;j<size+1;j++)
{
if((i==j)&&(i>0)&&(i<size))
c[i][j]=N[i].p;
else
c[i][j]=0;
if((i<size)&&(j<size))
{
if(i==j)
path[i][j]=i;
else
path[i][j]=0;
}
}
for(int k=1;k<size;k++)
for(int left=1;left<size-k;++left)
{
int right=left+k;
double sum=0.0;
c[left][right]=numeric_limits<double>::max();
for(int j=left;j<=right;j++)
{
sum+=N[j].p;
}
for(int i=left;i<=right;++i)
{
double temp=c[left][i-1]+c[i+1][right]+sum;
if(temp<c[left][right])
{
c[left][right]=temp;
path[left][right]=i;
}
}
}
//生成查找二叉树
toBinary(1,size-1);
}
void Binary::toBinary(int L,int R) //查找二叉树生成过程 例子:a am and egg if the two,
{ //初始化:L=1,R=7;i=path[1][7]=>i=3判断i!=L;i!=R。所以lef=path[1][2]=>lef=1;rig=path[4][7]=>rig=5;
if(L>=R) //所以N[3].left=&N[1];N[3].right=&N[5],
return; //下面分别递归找出1—2;4—7,两部分最优查询开销的子查找二叉树,即递归:toBinary(L,i-1);toBinary(i+1,R);
int i=path[L][R];
if(i==L)
{
int rig=path[i+1][R];
N[i].left=NULL;
N[i].right=&N[rig];
}
else if(i==R)
{
int lef=path[L][i-1];
N[i].left=&N[lef];
N[i].right=NULL;
}
else
{
int lef=path[L][i-1];
int rig=path[i+1][R];
N[i].left=&N[lef];
N[i].right=&N[rig];
}
toBinary(L,i-1);
toBinary(i+1,R);
}
void Binary::show()
{
generate();
int n=N.size()-1;
int i=path[1][n];
Node* t=&N[i];
cout<<"最少的开销:"<<c[1][n]<<endl;
cout<<"前序遍历生成的查找二叉树:"<<endl;
preprint(t);
}
void Binary::preprint(Node* t)//前序遍历输出生成的查找二叉树
{
if(t!=NULL)
{
cout<<t->name<<" ";
preprint(t->left);
preprint(t->right);
}
}
Algorithm-dynamic3.cpp
// Algorithm-dynamic3.cpp : 定义控制台应用程序的入口点。
//
//动态规划实验最优二叉查找树
#include "stdafx.h"
#include"dynamic.h"
#include<iostream>
#include<vector>
#include<string>
int _tmain(int argc, _TCHAR* argv[])
{
Binary b;
b.insert("a",0.22);
b.insert("am",0.18);
b.insert("and",0.20);
b.insert("egg",0.05);
b.insert("if",0.25);
b.insert("the",0.02);
b.insert("two",0.08);
b.show();
return 0;
}