1. 程式人生 > >動態規劃求解最長公共子序列(LCS)

動態規劃求解最長公共子序列(LCS)

      看了《演算法導論》中文第二版P208的動態規劃求解LCS問題,覺得很贊,但總覺得算導寫得有些晦澀,希望自己能寫得簡單易懂一些,純當鍛鍊了,歡迎指導交流。

      首先,子序列和子串是不一樣的。子串是連續的,而子序列中的元素組成可以是不連續的,但元素的位置下標一定是遞增的。以一個字串S = "abcdef"為例,字串S的一個子串是"abc",'cdef'這種連續的,而子序列可以是"abc",也可以是"af ",給人的直觀感覺就是用S中的字元拼湊成的,但f一定在a之前。

      我們設有兩個字串X和Y,其中,X={x0, x1, x2, ....xi },Y={y0, y1, y2, yj }。用lcs(i , j)表示字串X與Y的最長公共子序列的長度。

由動態規劃思想可知,問題的最優解是由子問題的最優解構成的,所以lcs(i,j) 的值與lcs(i-1, j-1), lcs(i-1, j), lcs(i, j-1) 都有一定的關係。

要確定lcs(i,j)的值,首先比較一下xi, yj 的值,有以下2種情況:

1. xi == yj,說明xi與yj 一定在最長公共子序列中,所以lcs (i , j) 是由lcs(i-1,j -1)之前的值決定的,即

     lcs(i,j) = lcs(i-1, j-1) + 1;

2. xi != yj ,設在最長公共子序列中的最後一個值為zk,可能zk == xi,也可能zk == yj,也可能zk與xi, yj的值都不同。

    這3種情況的分析如下:

     a. zk == xi, 那麼最長公共子序列取決於去掉yj的Y字串與X字串的最長公共子序列,

         即lcs(i, j) = lcs(i, j-1);

     b. 與a同理,若yj在最長公共子序列中,那麼lcs(i, j) = lcs(i - 1, j);

     c. zk與xi, yj的值都不同,那麼lcs( i, j ) 的值取決於去掉xi的X字串與去掉yj的Y字串的最長公共子序列的長度,即

         lcs(i, j) = lcs( i-1, j-1)。

     結合a,b,c的情況,因為最長公共子序列必有最大的長度,所以

     lcs(i, j) = max ( lcs( i-1, j) , lcs( i, j-1) )。這個公式之所以包含情況c,是因為在情況c下,最長公共子序列的最後一個

     值zk肯定是xi與yj之前的位置上的值,xi 與yj 在這種情況下對最長公共子序列的長度沒有影響力,所以在這種情況下,

      lcs( i-1, j) 和 lcs( i, j-1)都等於lcs (i -1, j -1),可歸併到公式中。

       因此,我們有:

      當xi == yj 時,lcs(i,j) = lcs(i-1, j-1) + 1;

      當xi != yj 時, lcs(i, j) = max ( lcs( i-1, j) , lcs( i, j-1) )。

        分析完畢,可以練練手了,POJ的1458題正是LCS的題。

       LCS的一般程式碼如下:

int LCS(string sx, string sy)
{
	int sizex = sx.length();
	int sizey = sy.length();

	//建立動態二維陣列
	int **lcs = new int*[sizex];
	for (int i = 0; i < sizex; ++i)
	{	
		lcs[i] = new int[sizey];
	}

	//求解LCS
	int max = 0;
	for (int i = 0; i < sizex; ++i)
	{
		for (int j = 0; j < sizey; ++j)
		{
			//計算初始值:lcs[0][x]和lcs[x][0]的值
			if (i == 0 || j == 0)
			{
				if (sx[i] == sy[j])
				{
					lcs[i][j] = 1;
				}
				else
				{
					if (i == 0 && j == 0)
						lcs[i][j] = 0;

					else if (i == 0 && j != 0)
						lcs[i][j] = lcs[i][j-1];

					else
                        lcs[i][j] = lcs[i-1][j];
				}

			}
			else  //其他lcs[i][j]的值
			{
				if (sx[i] == sy[j])
					lcs[i][j] = lcs[i-1][j-1] + 1;
				else 
					lcs[i][j] = lcs[i-1][j] > lcs[i][j-1] ? lcs[i-1][j] : lcs[i][j-1];
			}

			if (lcs[i][j] > max) 
				max = lcs[i][j];
		}
	}

	//釋放動態二維陣列
	for (int i = 0; i < sizex; ++i)
	{
		delete[] lcs[i];
	}
	delete[] lcs;

	return max;
}