荒废算法的学习已经快一年了,最近越来越认识到算法的重要性,虽然可能不去参加ACM比赛,但是算法也是一个程序员必备的一门课。让我有点难受是过去所学得算法似乎都没有真正地去搞懂过,有很多细节的地方都是以自己的想法掩盖过去,试图说服自己已经懂了,所以在算法的学习路上才会那么的坎坷(当然我现在仍然在算法学习路上)。其实很多时候都怕自己学算法不会变通,生搬硬套,也许是自己不够熟悉,练习不够。但不管如何,继续摸索。

昨天学习了动态规划的最长公共子序列问题。评判一个问题是否可以用
动态规划去解决时,需要判断该问题是否具有以下的两个特点。

  1. 最优子结构。最优子结构是指一个问题可以分解为若干的子问题,二这些子问题在结构上是与原问题一致的,只是问题的规模会越来越小。
  2. 重叠子问题。在一个问题被分解为若干的子问题后,可能会出现重叠的子问题,即一个问题会被求解多次,浪费资源。重叠子问题的解决可以使用数组去保存子问题的结果,当问题再次被分解为子问题时先判断该子问题是否已经被计算过了,如果被计算过就无须再次计算,直接使用子问题的结果。

最长公共子序列问题的分析
1.首先子序列可以是不连续的,比如字符串 ABCDE是一个序列,而 ACE是该字符串的子序列,ACE并没有相邻。
2. 以C[i] [j] 作为长度为i的字符串A和长度为j的字符串B的最长公共子序的长度。假设A[i]为字符串A的第i个元素,B[j]为字符串B的第j个元素.
3. 当A[i]==B[j]时, 此时他们的公共子序列的长度为A[i-1]和B[i-1]时候的最长公共子序列的长度加1,即C[i][j]=c[i-1][j-1].
4. 当A[i]!=B[j]时,需要判断以下三种情况:
(1)A[i]等于B[0]~B[j-1]中的一个,即A[i]对于最长公共子序列有贡献,此时的最长公共子序列的长度为 c[i][j-1]
(2)B[j]等于A[0]~A[i-1]中的一个,即B[i]对于最长公共子序列有贡献.此时的最长公共子序列的长度为 c[i-1][j]
(3)A[i]不等于B[0]~B[j-1]中的一个, B[j]不等于A[0]~A[i-1]中的一个 ,即A[i]和B[j]对于最长公共子序列的长度都没有贡献.此时的最长公共子序列的长度为 c[i]-1[j-1].
显然第一种情况的最长公共子序列长度都比第三种的大,所以我们只要考虑前两者,且要取两者中较大的一个。因此我们得到了该最长公共子序列的递推方程组。

我们可以通过递归不断地去分解问题。
也可以从子问题出发,把子问题计算出来,再去解决父问题,将子问题的求解结果保存到数组,下次便可以直接使用,同时无需去判断子问题是否已经解决过,因为是从子问题出发的。

这里我们使用了第二种方法

先考虑从c[0][j]和c[i][0]的问题,A串或B串长为0时,其最长公共子序列的长度为0,因此需要初始化为0;
这里c[i][j]的行和列都加了一,因为需要保存AB串长度为0时候其最长公共子序列的长度。

最长公共子序列代码

//fun为二维数组实现
//fun1为滚动数组实现
#include <stdio.h>

#define m 100 
#define n 100

int c[m+1][n+1]={
   0};//c[m][n]为长度为m的A的子序列与长度为n的B的子序列的最长公共子序列
//思路:
//如果xi=xj,则最长公共子序列为c[i-1][j-1]+1
//图个xi!=xj,xi最长公共子序列为
void init(){
   //长度为0的序列与另外一条序列的最长公共子序列为0
	for (int i=0;i<=m;i++)
	c[i][0]=0;
	for (int j=0;j<=n;j++)
		c[0][j]=0;
}
char A[m]="ABCDEFGCC";
char B[n]="CREFYSDJNHGCCS";
/*动态规划,非递归,因为是自下而上的,先计算了子问题,在往上计算大的问题 *因为一开始就对最小子问题有了初始化(init函数),每次的计算都需要用到上次计算的结果, *故用数组保存上次的结果,下次便可以直接使用 */

//程序的编写需要注意的是数组长度问题,
int max(int a,int b){
   
	return a>b?a:b;
}
int fun(int i,int j){
   
	if(A[i]==B[j]){
   
		//c数组行和列都是比AB数组长度多一的
		//因为AB两序列数组下标为0其长度为1而不是0
		//所以c数组行和列增加一来保存AB序列长度为0时候的其最长公共子序列的长度
		//也就是init函数的所要做的操作
		c[i+1][j+1]=c[i][j]+1;
	}else{
   
		c[i+1][j+1]=max(c[i][j+1],c[i+1][j]);
	}
	return c[i+1][j+1];
}



//滚动数组
int c1[2][n]={
   0};//c1选择为较小的子序列长度

int fun1(int i,int j){
   
	if(A[i]==B[j]){
   //判断两序列元素是否相等
		//c1数组长度是比AB数组长度多一的
		//因为AB两序列数组下标为0其长度为1而不是0
		//所以c1数组增加一来保存AB序列长度为0时候的其最长公共子序列的长度
		c1[(i+1)%2][j+1]=c1[i%2][j]+1;

	}else{
   
		c1[(i+1)%2][j+1]=max(c1[(i+1)%2][j],c1[(i)%2][j+1]);
	}
}

void common(int lm,int ln){
   //普通二维数组来保存最长公共子序列长度
printf("%s\n", "common method result:");
	for(int i=0;i<lm;i++)
		for(int j=0;j<ln;j++){
   
			fun(i,j);//fun是动态规划算法的具体实现
			printf("%d ",c[i][j] );
			if(j==ln-1){
   
				printf("\n");
			}
			
		}
	printf("%d\n lm:%d ln:%d",c[lm][ln],lm,ln);//输出AB数组的长度以及c[m][n]的值
}

//由于在使用二维数组进行动态规划时,
//每次使用到的数据只有前一个和上一行的数据
//因此可以使用一行数组进行滚动,从而缩小空间
void roll(int lm,int ln){
   //滚动数组来保存最长公共子序列长度
	printf("\n%s\n", "roll method result:");
	printf("lm :%d\n", lm);
	for(int i=0;i<lm;i++){
   
			for (int j=0;j<ln;j++){
   
				//fun1是动态规划算法的具体实现
				fun1(i,j);
				//把j作为列,i为行;选择AB两序列中长度较短的保存;这里并没有加判断语句进行选择
				//
			}
			// printf("\n");
			// for (int j=0;j<ln;j++){
   
			// printf("%d ",c1[i%2][j] );
			// }
			// printf("\n");
			// for (int j=0;j<ln;j++){
   
			// printf("%d ",c1[(i+1)%2][j] );
			// }
			// printf("\n");
			
		}
		printf("\n");
		for(int i=0;i<=ln;i++){
   
			printf("%d ", c1[lm%2][i]);//最后输出第lm%2的数组(因为长度为lm)
		}

}


int main(){
   
	init();
	int lm=0,ln=0;
	while(A[lm]!='\0'){
   
		lm++;
	}
	
	while(B[ln]!='\0'){
   
		ln++;
	}
	common(lm,ln);//二维数组实现
	roll(lm,ln);//滚动数组实现
	

	return 0;
}


另外的最长公共子串的问题跟最长公共子序列其实是差不多的,最长公共子串是相邻的,需要在判断A[i] 和B[i]不等的时候

附上最长公共子串的代码,可能实现有点出入

#include <stdio.h>

#define m 100 
#define n 100

int c[m+1][n+1]={
   0};
void init(){
   
	for (int i=0;i<=m;i++)
	c[i][0]=0;
	for (int j=0;j<=n;j++)
	c[0][j]=0;
}
char A[m]="ABCDEFG";
char B[n]="BCACDEFGCA";

int max(int a,int b){
   
	return a>b?a:b;
}
int fun(int i,int j){
   
	
	if(A[i]==B[j]){
   
		c[i+1][j+1]=c[i][j]+1;
	}else{
   
		c[i+1][j+1]=0;
	}
	return c[i][j];
}

int main(){
   
	init();
	int lm=0,ln=0;
	while(A[lm++]!='\0'){
   
	}
	
	while(B[ln++]!='\0'){
   
	}

	for(int i=0;i<lm-1;i++)
		for(int j=0;j<ln-1;j++){
   
			fun(i,j);
			//printf("%d\n",c[i][j] );
			
		}
		int maxn=0;
	for (int i=0;i<lm;i++){
   
		for (int j=0;j<ln;j++){
   
			if(maxn<c[i][j]) maxn=c[i][j];
			printf("%d ", c[i][j]);
			if(j==ln-1){
   
				printf("\n");
			}
		}
	}
	printf("%d\n lm:%d ln:%d \n%s",maxn,lm,ln,B);
	return 0;
}

算法学习路途遥远,一点一点积累。。。。。