最优二分搜索树

 

构建最优二分搜索树

问题描述:

       首先看个例子,如果有S = {5,7,10,12,14,15,18},我们可以构建一个二分搜索树,所谓二分搜索树(Binary Search Tree),就或者是一棵空树,或者是具有下列性质的二分树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二分搜索树。现在,我们可以构建一下两种树:

                        

       现在问题来了,如果在我们的搜索中15~18范围搜索的次数为80%,节点5的搜索次数为10%,其余的节点和节点以外的范围搜索次数频率相等,共计10%。显而易见,两种不同结构的二分搜索树的效率大不一样。在此,结合每个搜索到的节点和范围的频率(记为p[1:n],q[0:n])我们给出搜索树的总耗费公式为:

        

       这里depth(al)为al在该树中的深度,depth(bl)为bl在该树中的深度。

       如今的问题就是,对于一个给定的序列,{b0,a1,b1……an,bn},其中a1,a2……an是实节点,b0,b1,b2……bn是虚节点(就是二分搜索树最终找不到实节点的范围),如何找出并构建一个总耗费最小的二分搜索树?

问题求解:

     分析:

            假定T0是棵最优二分搜索树,它的根是ak(第k小的数)。则ak的左子树中必然包含了{a1…ak-1}, ak的右子树中必然包含了{ak+1…an}。而且,左子树一定是关于{a1…ak-1}的最优二分搜索树,右子树一定是关于{ak+1…an}的最优二分搜索树。(否则,用更优的子树代替当前的子树,其总耗费比T0的更小,矛盾。)

       现在考虑二分搜索树问题的性质,

       ① 根据二分搜索树的性质,任何一棵子树中结点的编号都是连续的。而且,最优树中的任何一棵子树,也必然是关于子树中结点的最优树。因此,最优二分搜索树具有最优子结构性质。

       ② 这样,若规模为m≤n-1的最优子树均已知,就可以通过逐一计算以a1,a2,…,an为根的树的耗费,来确定(使耗费达到最小的)根ak并找出最优二分搜索树。

       在上述计算中,规模较小的最优子树在计算中要多次被用到,故该问题具有高度重复性。

       综合 ①和②的特性,我们就考虑动态规划来求解这个问题。

 

       在所有由结点bi,ai+1,bi+1,…,aj,bj构成的树中,把耗费最小的树记为Tij。(注意该树中不含ai但含bi)。

       若由结点bi,ai+1,bi+1,…,aj,bj构成的树以ak作为根 (i+1≤k≤j),

           则bi,ai+1,bi+1,…,ak-1,bk-1必然在其左子树中,

           则bk,ak+1,bk+1,…,aj,bj必然在其右子树中,

       以ak为根的树有很多,但其中耗费最小的树必然是以Ti,k-1为其左子树,以Tk,j为其右子树。

       记cij是最优子树Tij的耗费, 则ci,k-1是最优子树Ti,k-1的耗费,ck,j是最优子树Tk,j的耗费。

       考察以ak (i+1≤k≤j)为根、由结点bi,ai+1,bi+1,…,aj,bj构成的、耗费最小的树的总耗费:根据上述讨论,

该树的左子树必然是Ti,k-1,右子树必然是Tk,j 。该树的总耗费可分为三部分:左子树、右子树和根所产生的耗费。

       紧接着就将Ti,k-1和Tk,j接到ak上,此处与每个左右子树中节点来说,搜索的耗费都增加了,因为每个节点的深度都增加了1,因此,引入一个公式,来表示子树在接入根节点的时候增加的耗费,

             

        由于Ti,k-1作为左子树接到结点ak之下时,其耗费增加wi,k-1,故左子树的耗费为:ci,k-1+ wi,k-1

        同理,右子树的耗费为:ck,j+wk,j

        由于根ak的深度为0,按定义,根的耗费为pk

        因此,以ak 为根、耗费最小的树的总耗费为:ci,k-1+ wi,k-1+ckj+wk,j+pk

        注意到,

                   wi,k-1=qi+pi+1+qi+1+…+pk-1+qk-1

                   wk,j=qk+pk+1+qk+1+…+pj+qj

        从而有wi,k-1+wkj+pk = qi+pi+1+qi+1+…+pk-1+qk-1+ pk +qk+pk+1+qk+1+…+pj+qj = wij

        由此得到,以ak 为根、耗费最小的树的总耗费为:ci,k-1+ckj+wi,j由于pi(i=1,2,…,n)和 qj(j=0,1,2,…,n)在初始时已经知道,因此若wi,j-1已知,则根据wi,j= wi,j-1+pj + qj可以计算出wij。故当ci,k-1与ckj已知时,以ak 为根的树的最小总耗费在O(1)时间就可以计算出来。

        这样,分别计算以ai+1,ai+2,…,aj为根、含有结点bi,ai+1,bi+1,…,aj,bj的树的最小总耗费,再从中选出耗费最小的树,即可得最优子树Tij

        因此,最优子树Tij的耗费cij= {ci,k-1+ckj+wij}。

算法描述:

        递推求cij及记录Tij的根的算法

        初始时 wii←qi(i=1,2,…,n);cii←0;/*∵空树Tii耗费为0(i=1,2,…,n)。*/

        for l←1 to n do /*计算规模为l(树中真实结点个数)的最优二分搜索树,从含1个结点直算到含n个结点*/

            { for i←0 to n-l do    /*真实结点个数为l个的树共有(n-l+1)个,i为树中结点起始下标,从0直到n-l。*/

                 {   j←i+l;                     /* j为树中最后一个结点的下标*/

                     wi,j←wi,j-1+pj + qj ;  /*计算出规模为l (l=j-i)、当前所需的wi,j*/

                     cij← {ci,k-1+ckj+wij};    /*在前一轮或前几轮,规模小的ci,k-1和ckj已计算出来了*/

                     rij←k’;      /*k’为使得{ci,k-1+ckj+wij}为最小的k值*/

                  }                 /*即ak’是最优子树Tij的根,记录在案*/

             }

 

        构建最优二分搜索树,

        设Tij的根为ak (rij记录到的值是k),则从根开始建结点。

        Build-tree(i,j,r,A)                    /*建立最优子树Tij*/

        {

           If i≥j return null;

           pointer←newnode(nodetype);

           k←rij;                               /*必有i < k ≤j*/

           pointer_key-value←A[k];             /*A[k]即ak*/

           pointer_leftson←Buildtree(i,k-1,r,A); /*建立最优左子树Ti,k-1*/

           pointer_rightson←Buildertree(k,j,r,A); /*建立最优右子树Tk,j*/

           return pointer;

        }

        调用Build-tree(0,n,r,A)则可以建成最优二分搜索树。

C语言实现与运行结果截图:

 

#include <stdio.h>
#include <stdlib.h>

#define		MAX_LEN		256

typedef struct BtreeNode{
	int data;
	BtreeNode* lchild;
	BtreeNode* rchild;
}BtreeNode,*BtreePtr;

BtreePtr build_most_value_btree(int st,int ed,int (*T)[MAX_LEN],int *num)
{
	if(st >= ed) return NULL;
	BtreePtr  p;
	p=(BtreePtr)malloc(sizeof(BtreeNode));
	p->data=num[T[st][ed]];
	p->lchild=build_most_value_btree(st,T[st][ed]-1,T,num);
	p->rchild=build_most_value_btree(T[st][ed],ed,T,num);
	return p;
}


int main()
{
	int index,tmp;
	int n,i,j,k,l;
	int P[MAX_LEN],Q[MAX_LEN];
	int num[MAX_LEN],W[MAX_LEN][MAX_LEN],C[MAX_LEN][MAX_LEN],T[MAX_LEN][MAX_LEN];

	while(1==scanf("%d",&n)){
		if(n > 0){
			for(i=1;i<=n;i++)
				scanf("%d",num+i);
			for(i=1;i<=n;i++)
				scanf("%d",P+i);
			for(i=0;i<=n;i++)
				scanf("%d",Q+i);
			break;
		}
		printf("invalid n.\n");
	}

	//initial tabs
	for(i=0;i<=n;i++){
		W[i][i]=Q[i];
		C[i][i]=0;
	}

	for(l=1;l<=n;l++)
	{
		for(i=0;i < n;i++){
			j=i+l;
			W[i][j]=W[i][j-1]+P[j]+Q[j];

			//find the minium root
			index=i+1;tmp=C[i][i]+C[i+1][j];
			for(k=i+2;k<=j;k++){
				if(C[i][k-1]+C[k][j] < tmp){
					tmp=C[i][k-1]+C[k][j];
					index=k;
				}
			}
			C[i][j]=C[i][index-1]+C[index][j]+W[i][j];
			T[i][j]=index;
		}
	}

	printf("cost=%d,root=%d\n",C[0][n],T[0][n]);

	BtreePtr mvbt=build_most_value_btree(0,n,T,num);

	return 0;
}


That’s all~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值