天天看點

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

      1、問題描速:  

     設 S={x1, x2, ···, xn} 是一個有序集合,且x1, x2, ···, xn表示有序集合的二叉搜尋樹利用二叉樹的頂點存儲有序集中的元素,而且具有性質:存儲于每個頂點中的元素x 大于其左子樹中任一個頂點中存儲的元素,小于其右子樹中任意頂點中存儲的元素。二叉樹中的葉頂點是形如(xi, xi+1) 的開區間。在表示S的二叉搜尋樹中搜尋一個元素x,傳回的結果有兩種情形:

    (1) 在二叉樹的内部頂點處找到: x = xi

    (2) 在二叉樹的葉頂點中确定: x∈ (xi , xi+1)

    設在情形(1)中找到元素x = xi的機率為bi;在情形(2)中确定x∈ (xi , xi+1)的機率為ai。其中約定x0= -∞ , xn+1= + ∞ ,有

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

    集合{a0,b1,a1,……bn,an}稱為集合S的存取機率分布。    

   最優二叉搜尋樹:在一個表示S的二叉樹T中,設存儲元素xi的結點深度為ci;葉結點(xj,xj+1)的結點深度為dj。

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

    注:在檢索過程中,每進行一次比較,就進入下面一層,對于成功的檢索,比較的次數就是所在的層數加1。對于不成功的檢索,被檢索的關鍵碼屬于那個外部結點代表的可能關鍵碼集合,比較次數就等于此外部結點的層數。對于圖的内結點而言,第0層需要比較操作次數為1,第1層需要比較2次,第2層需要3次。

     p表示在二叉搜尋樹T中作一次搜尋所需的平均比較次數。P又稱為二叉搜尋樹T的平均路長,在一般情況下,不同的二叉搜尋樹的平均路長是不同的。對于有序集S及其存取機率分布(a0,b1,a1,……bn,an),在所有表示有序集S的二叉搜尋樹中找出一棵具有最小平均路長的二叉搜尋樹。    

     設Pi是對ai檢索的機率。設qi是對滿足ai<X<ai+1,0<=i<=n的辨別符X檢索的機率, (假定a0=--∞且an+1=+ ∞)。

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

      對于有n個關鍵碼的集合,其關鍵碼有n!種不同的排列,可構成的不同二叉搜尋樹有

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

棵。(n個結點的不同二叉樹,卡塔蘭數)。如何評價這些二叉搜尋樹,可以用樹的搜尋效率來衡量。例如:辨別符集{1, 2, 3}={do, if, stop}可能的二分檢索樹為:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

     若P1=0.5, P2=0.1, P3=0.05,q0=0.15, q1=0.1, q2=0.05, q3=0.05,求每棵樹的平均比較次數(成本)。     

     Pa(n)=1 × p1 + 2 × p2+3 × p3 + 1×q0 +2×q1+ 3×( q2 + q3 ) =1 × 0.5+ 2 × 0.1+3 ×0.05 + 1×0.05 +2×0.1+ 3×( 0.05 + 0.05 ) =1.5

     Pb(n)=1 × p1 + 2 × p3+3 × p2 + 1×q0 +2×q3 + 3×( q1 + q2 ) =1 × 0.5+ 2 × 0.05 + 3 ×0.1 + 1×0.15 +2×0.05+ 3×( 0.1 + 0.05 ) =1.6

     Pc(n)=1 × p2 + 2 × (p1 +  p3) + 2×(q0 +q1 +q2 + q3 ) =1 × 0.1+ 2 × (0.5 + 0.05) + 2×(0.15 + 0.1 + 0.05 + 0.05) =1.9

     Pd(n)=1 × p3 + 2 × p1+3 × p2 + 1 × q3+2 × q0 +3 × (q1+ q2) =1 × 0.05 + 2 × 0.5 + 3 × 0.1 + 1×0.05 + 2 × 0.15 + 3 × (0.1 + 0.05) =2.15

     Pe(n)=1 × p3 + 2 × p2+3 × p1 + 1 × q3+2 × q2 +3 × (q0 + q1) =1 × 0.05 + 2 × 0.1+ 3 × 0.5 + 1×0.05 + 2 × 0.15 + 3 × (0.15 + 0.1) =2.85

     是以,上例中的最小平均路長為Pa(n)=1.5。

     可以得出結論:結點在二叉搜尋樹中的層次越深,需要比較的次數就越多,是以要構造一棵最小二叉樹,一般盡量把搜尋機率較高的結點放在較高的層次。

     2、最優子結構性質:

     假設選擇 k為樹根,則 1, 2, …, k-1 和a0, a1, …, ak-1 都将位于左子樹 L 上,其餘結點 (k+1, …, n 和 ak, ak+1, …, an)位于右子樹 R 上。設COST(L) 和COST(R) 分别是二分檢索樹T的左子樹和右子樹的成本。則檢索樹T的成本是:P(k)+ COST(L) + COST(R) + …… 。若 T 是最優的,則上式及 COST(L) 和COST(R) 必定都取最小值。

    證明:二叉搜尋樹T 的一棵含有頂點xi , ··· , xj和葉頂點(xi-1 , xi ) , ··· , ( xj , xj+1)的子樹可以看作是有序集{ xi , ··· , xj}關于全集為 { xi-1 , xj+1 }的一棵二叉搜尋樹(T自身可以看作是有序集) 。根據S 的存取分布機率,在子樹的頂點處被搜尋到的機率是:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

。{xi , ··· , xj}的存儲機率分布為{ai-1, bi, …, bj, aj },其中,ah,bk分别是下面的條件機率:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

     設Tij是有序集{xi , ··· , xj}關于存儲機率分布為{ai-1, bi, …, bj, aj}的一棵最優二叉搜尋樹,其平均路長為pij,Tij的根頂點存儲的元素xm,其左子樹Tl和右子樹Tr的平均路長分别為pl和pr。由于Tl和Tr中頂點深度是它們在Tij中的深度減1,是以得到:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

     由于Ti是關于集合{xi , ··· , xm-1}的一棵二叉搜尋樹,故Pl>=Pi,m-1。若Pl>Pi,m-1,則用Ti,m-1替換Tl可得到平均路長比Tij更小的二叉搜尋樹。這與Tij是最優二叉搜尋樹沖突。故Tl是一棵最優二叉搜尋樹。同理可證Tr也是一棵最優二叉搜尋樹。是以最優二叉搜尋樹問題具有最優子結構性質。

     3、遞推關系:

     根據最優二叉搜尋樹問題的最優子結構性質可建立計算pij的遞歸式如下:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

初始時:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

     記 wi,j pi,j為m(i,j) ,則m(1,n)=w1,n p1,n=p1,n為所求的最優值。計算m(i,j)的遞歸式為:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

     4、求解過程:

    1)沒有内部節點時,構造T[1][0],T[2][1],T[3][2]……,T[n+1][n]

    2)構造隻有1個内部結點的最優二叉搜尋樹T[1][1],T[2][2]…, T[n][n],可以求得m[i][i] 同時可以用一個數組存做根結點元素為:s[1][1]=1, s[2][2]=2…s[n][n]=n

    3)構造具有2個、3個、……、n個内部結點的最優二叉搜尋樹。

    ……

    r ( 起止下标的差)

    0   T[1][1], T[2][2]       , …,     T[n][n],

    1   T[1][2], T[2][3], …,T[n-1][n],

    2   T[1][3], T[2][4], …,T[n-2][n],

    ……

    r   T[1][r+1], T[2][r+2], …,T[i][i+r],…,T[n-r][n]

    ……

    n-1   T[1][n] 

    具體代碼如下:     

//3d11-1 最優二叉搜尋樹 動态規劃
#include "stdafx.h"
#include <iostream> 
using namespace std;

const int N = 3;

void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);
void Traceback(int n,int i,int j,int **s,int f,char ch);

int main()
{
	double a[] = {0.15,0.1,0.05,0.05};
	double b[] = {0.00,0.5,0.1,0.05};

	cout<<"有序集的機率分布為:"<<endl;
	for(int i=0; i<N+1; i++)
	{
		cout<<"a"<<i<<"="<<a[i]<<",b"<<i<<"="<<b[i]<<endl;
	}

	double **m = new double *[N+2];
	int **s = new int *[N+2];
	double **w =new double *[N+2];

	for(int i=0;i<N+2;i++)  
    {  
		m[i] = new double[N+2];  
		s[i] = new int[N+2];  
		w[i] = new double[N+2];  
    } 

	OptimalBinarySearchTree(a,b,N,m,s,w);
	cout<<"二叉搜尋樹最小平均路長為:"<<m[1][N]<<endl;
	cout<<"構造的最優二叉樹為:"<<endl;
	Traceback(N,1,N,s,0,'0');

	for(int i=0;i<N+2;i++)  
    {  
		delete m[i];
		delete s[i];
		delete w[i];
    } 
	delete[] m;
	delete[] s;
	delete[] w;
	return 0;
}

void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)
{
	//初始化構造無内部節點的情況
	for(int i=0; i<=n; i++)
	{
		w[i+1][i] = a[i];
		m[i+1][i] = 0;
	}

	for(int r=0; r<n; r++)//r代表起止下标的差
	{
		for(int i=1; i<=n-r; i++)//i為起始元素下标
		{
			int j = i+r;//j為終止元素下标

			//構造T[i][j] 填寫w[i][j],m[i][j],s[i][j]
			//首選i作為根,其左子樹為空,右子樹為節點
			w[i][j]=w[i][j-1]+a[j]+b[j];
			m[i][j]=m[i+1][j];
			s[i][j]=i;

			//不選i作為根,設k為其根,則k=i+1,……j
			//左子樹為節點:i,i+1……k-1,右子樹為節點:k+1,k+2,……j
			for(int k=i+1; k<=j; k++)
			{
				double t = m[i][k-1]+m[k+1][j];

				if(t<m[i][j])
				{
					m[i][j]=t;
					s[i][j]=k;//根節點元素
				}
			}
			m[i][j]+=w[i][j];
		}
	}
}

void Traceback(int n,int i,int j,int **s,int f,char ch)
{
	int k=s[i][j];
	if(k>0)
	{
		if(f==0)
		{
			//根
			cout<<"Root:"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;
		}
		else
		{
			//子樹
			cout<<ch<<" of "<<f<<":"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;
		}

		int t = k-1;
		if(t>=i && t<=n)
		{
			//回溯左子樹
			Traceback(n,i,t,s,k,'L');
		}
		t=k+1;
		if(t<=j)
		{
			//回溯右子樹
			Traceback(n,t,j,s,k,'R');
		}
	}
}
           

         4、構造最優解:

   算法OptimalBinarySearchTree中用s[i][j]儲存最優子樹T(i,j)的根節點中的元素。當s[i][n]=k時,xk為所求二叉搜尋樹根節點元素。其左子樹為T(1,k-1)。是以,i=s[1][k-1]表示T(1,k-1)的根節點元素為xi。依次類推,容易由s記錄的資訊在O(n)時間内構造出所求的最優二叉搜尋樹。

     5、複雜度分析與優化:

   算法中用到3個數組m,s和w,故所需空間複雜度為O(n^2)。算法的主要計算量在于計算

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

。對于固定的r,它需要的計算時間O(j-i+1)=O(r+1)。是以算法所耗費的總時間為:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

。事實上,由《動态規劃加速原理之四邊形不等式》可以得到:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

而此狀态轉移方程的時間複雜度為O(n^2)。由此,對算法改進後的代碼如下:

//3d11-1 最優二叉搜尋樹 動态規劃加速原理 四邊形不等式
#include "stdafx.h"
#include <iostream> 
using namespace std;

const int N = 3;

void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);
void Traceback(int n,int i,int j,int **s,int f,char ch);

int main()
{
	double a[] = {0.15,0.1,0.05,0.05};
	double b[] = {0.00,0.5,0.1,0.05};

	cout<<"有序集的機率分布為:"<<endl;
	for(int i=0; i<N+1; i++)
	{
		cout<<"a"<<i<<"="<<a[i]<<",b"<<i<<"="<<b[i]<<endl;
	}

	double **m = new double *[N+2];
	int **s = new int *[N+2];
	double **w =new double *[N+2];

	for(int i=0;i<N+2;i++)  
    {  
		m[i] = new double[N+2];  
		s[i] = new int[N+2];  
		w[i] = new double[N+2];  
    } 

	OptimalBinarySearchTree(a,b,N,m,s,w);
	cout<<"二叉搜尋樹最小平均路長為:"<<m[1][N]<<endl;
	cout<<"構造的最優二叉樹為:"<<endl;
	Traceback(N,1,N,s,0,'0');

	for(int i=0;i<N+2;i++)  
    {  
		delete m[i];
		delete s[i];
		delete w[i];
    } 
	delete[] m;
	delete[] s;
	delete[] w;
	return 0;
}

void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)
{
	//初始化構造無内部節點的情況
	for(int i=0; i<=n; i++)
	{
		w[i+1][i] = a[i];
		m[i+1][i] = 0;
		s[i+1][i] = 0;
	}

	for(int r=0; r<n; r++)//r代表起止下标的差
	{
		for(int i=1; i<=n-r; i++)//i為起始元素下标
		{
			int j = i+r;//j為終止元素下标
			int i1 = s[i][j-1]>i?s[i][j-1]:i;
			int j1 = s[i+1][j]>i?s[i+1][j]:j;

			//構造T[i][j] 填寫w[i][j],m[i][j],s[i][j]
			//首選i作為根,其左子樹為空,右子樹為節點
			w[i][j]=w[i][j-1]+a[j]+b[j];
			m[i][j]=m[i][i1-1]+m[i1+1][j];
			s[i][j]=i1;

			//不選i作為根,設k為其根,則k=i+1,……j
			//左子樹為節點:i,i+1……k-1,右子樹為節點:k+1,k+2,……j
			for(int k=i1+1; k<=j1; k++)
			{
				double t = m[i][k-1]+m[k+1][j];

				if(t<m[i][j])
				{
					m[i][j]=t;
					s[i][j]=k;//根節點元素
				}
			}
			m[i][j]+=w[i][j];
		}
	}
}

void Traceback(int n,int i,int j,int **s,int f,char ch)
{
	int k=s[i][j];
	if(k>0)
	{
		if(f==0)
		{
			//根
			cout<<"Root:"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;
		}
		else
		{
			//子樹
			cout<<ch<<" of "<<f<<":"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;
		}

		int t = k-1;
		if(t>=i && t<=n)
		{
			//回溯左子樹
			Traceback(n,i,t,s,k,'L');
		}
		t=k+1;
		if(t<=j)
		{
			//回溯右子樹
			Traceback(n,t,j,s,k,'R');
		}
	}
}
           

運作結果如圖:

0020算法筆記——【動态規劃】最優二叉搜尋樹問題

繼續閱讀