1. 程式人生 > >【軟考總結】-動態規劃法--最長公共子序列

【軟考總結】-動態規劃法--最長公共子序列

一、什麼是最長公共子序列?

   公共子序列:字元序列的子序列是指從給定字元序列中隨意地(不一定連續)去掉若干個字元(可能一個也不去掉)後所形成的字元序列。令給定的字元序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一個嚴格遞增下標序列<i0,i1,…,ik-1>,使得對所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一個子序列。(子序列Y中的字元在X中不一定是連續的。

   最長公共子序列:找到的所有子序列中,字元最長的序列。

二、如何求解?

       方法一:窮舉法:列出X的所有子序列,一一檢查是否是Y的子序列,記錄所發現的公共子序列,最終求出最長公共子序列

       無疑,方法一是很費時費力的;

       方法二:刻畫最長公共子序列問題的最優子結構

求解:

        引進一個二維陣列c[][],用c[i][j]記錄X[i]與Y[j] 的LCS的長度,b[i][j]記錄c[i][j]是通過哪一個子問題的值求得的,以決定搜尋的方向

        我們是自底向上進行遞推計算,那麼在計算c[i,j]之前,c[i-1][j-1],c[i-1][j]與c[i][j-1]均已計算出來。此時我們根據X[i]= Y[j]還是X[i] != Y[j],就可以計算出c[i][j]。

         問題的遞迴式寫成:

                                        


這個遞迴式可以解釋為:

     1、當比較不開始時,兩個序列沒有公共序列;

     2、當兩個序列比較時,比較的兩個字元相同,公共序列的長度為前一個已經計算出的公共序列的長度+1

     3、當兩個序列比較時,比較的兩個字元不同,公共序列的長度有兩個,分別是兩個序列在前一個狀態下所求出的公共序列的長度,要這兩個公共序列中最長的一個。

注意,這裡c[i][j]是公共序列的長度,不是具體的公共序列,以二維陣列的方式儲存,ij可以看做是在座標系中的橫縱座標。

                   

Java程式碼如下:

Public class LCSProblem
{
	/**
	 *初始化
	**/
	
	publicstaticvoidmain(String[]args)
	{
		String[]x={"","A","B","C","B","D","A","B"};   //初始化序列X;
		String[]y={"","B","D","C","A","B","A"};       //初始化序列Y;
		int[][]b=getLength(x,y);                      //將兩個序列的最長公共序列的長度值記錄在二維陣列b中;
		Display(b,x,x.length-1,y.length-1);           //輸出;
	
	/**
	 *計算最長公共子序列的長度
	**/
	Public static int[][]getLength(String[]x,String[]y)
	{
		int[][]b=new int[x.length][y.length];         //初始化陣列b,記錄c[i][j]是通過哪一個子問題的值求得的,以決定搜尋的方向。
		int[][]c=new int[x.length][y.length];         //初始化陣列c,記錄X[i]與Y[j] 的LCS 的長度。
		
		//填充矩陣
		for(int i=1;i<x.length;i++)
		{
			for(int j=1;j<y.length;j++)
			{
				if(x[i]==y[j])
				{
					c[i][j]=c[i-1][j-1]+1;
					b[i][j]=1;   //1代表指向左上方的箭頭
				}
				elseif(c[i-1][j]>=c[i][j-1])
				{
					c[i][j]=c[i-1][j];
					b[i][j]=0;   //0代表指向上方的箭頭
				}
				else
				{
					c[i][j]=c[i][j-1];
					b[i][j]=-1;  //-1代表指向左方的箭頭
				}
			}
		}
		Return b;
	}

用箭頭的指向,表示序列中有公共的值時,該解是在求解哪個子問題的基礎上得來。

/**
 *輸出最長公共子序列
**/

void PrintLCS(int b[][], char x, int i, int j)
{
    if(i == 0 || j == 0)         //兩個字串中任意一個長度為0;
        return  null;
    if(b[i][j] == 1)                  //箭頭指向左上方
    {
        PrintLCS(b, x, i-1, j-1);
        printf("%c ", x[i-1]);     //輸出兩個序列相同的字元
    }
    else if(b[i][j] == 0)      //箭頭指向上方
        PrintLCS(b, x, i-1, j);
    else                                           //箭頭指向左方
        PrintLCS(b, x, i, j-1);
}

三、複雜度:

      時間複雜度:構建矩陣我們花費了O(MN)的時間,回溯時我們花費了O(M+N)的時間,兩者相加最終我們花費了O(MN)的時間。

      空間複雜度:構建矩陣我們花費了O(MN)的空間,標記函式也花費了O(MN)的空間,兩者相加最終我們花費了O(MN)的空間。

四、應用:

查重,相似度分析,查詢最長遞增子序列等;

這些應用,你有沒有想到呢?

參考資料: