最长递增子序列解释为要求长度为i的序列的Ai{a1,a2,……,ai}最长递增子序列,需要先求出序列Ai-1{a1,a2,……,ai-1}中以各元素(a1,a2,……,ai-1)作为最大元素的最长递增序列,然后把所有这些递增序列与ai比较,如果某个长度为m序列的末尾元素aj(j<i)比ai要小,则将元素ai加入这个递增子序列,得到一个新的长度为m+1的新序列,否则其长度不变,将处理后的所有i个序列的长度进行比较,其中最长的序列就是所求的最长递增子序列。
- unsigned int LISS(const int array[], size_t length, int result[])
- {
- unsigned int i, j, k, max;
- //变长数组参数,C99新特性,用于记录当前各元素作为最大元素的最长递增序列长度
- unsigned int liss[length];
- //前驱元素数组,记录当前以该元素作为最大元素的递增序列中该元素的前驱节点,用于打印序列用
- unsigned int pre[length];
- for(i = 0; i < length; ++i)
- {
- liss[i] = 1;
- pre[i] = i;
- }
- for(i = 1, max = 1, k = 0; i < length; ++i)
- {
- //找到以array[i]为最末元素的最长递增子序列
- for(j = 0; j < i; ++j)
- {
- //如果要求非递减子序列只需将array[j] < array[i]改成<=,
- //如果要求递减子序列只需改为>
- if(array[j] < array[i] && liss[j] + 1> liss[i])
- {
- liss[i] = liss[j] + 1;
- pre[i] = j;
- //得到当前最长递增子序列的长度,以及该子序列的最末元素的位置
- if(max < liss[i])
- {
- max = liss[i];
- k = i;
- }
- }
- }
- }
- //输出序列
- i = max - 1;
- while(pre[k] != k)
- {
- result[i--] = array[k];
- k = pre[k];
- }
- result[i] = array[k];
- return max;
- }
一个字符串的子序列,是指从该字符串中去掉任意多个字符后剩下的字符在不改变顺序的情况下组成的新字符串。
最长公共子序列,是指多个字符串可具有的长度最大的公共的子序列。
而这样问题恰可以用动态规划求解,动态规划即通过子问题的递归,求子问题最优解,而动态规划中通过寻找子问题最优解得出问题最优解又称最优子结构。动态规划在这问题中由四步:1.刻画最优解的结构特征,在本题中,设X={x1,x2, ,xn}Y={y1,y2,y3, ,ym}Z={z1,z2, zk}为X和Y的LCS(longest common squence),根据动态规划分析思路,当xn=ym时,Z(k-1)是X(n-1)与Y(m-1)的最优子序列,xn!=ym时,xn!=zk则Zk是X(n-1)与Y的LCS,xm!=zk则Zk是X与Y(m-1)的LCS,由此可知,当xn!=ym时,只需求二者中最大值,2.建立递归式3.计算LCS的长度,通过一个储存表与标记表解决问题4.求解问题
动态规划求最长公共子序列的长度
动态规划采用二维数组来标识中间计算结果,避免重复的计算来提高效率。
1)最长公共子序列的长度的动态规划方程
设有字符串a[0...n],b[0...m],下面就是递推公式。字符串a对应的是二维数组num的行,字符串b对应的是二维数组num的列。
另外,采用二维数组flag来记录下标i和j的走向。数字"1"表示,斜向下;数字"2"表示,水平向右;数字"3"表示,竖直向下。这样便于以后的求解最长公共子序列。以任意字符为例分析:
#include<stdio.h> #include<string.h> char a[500],b[500]; char num[501][501]; ///记录中间结果的数组 char flag[501][501]; ///标记数组,用于标识下标的走向,构造出公共子序列 void LCS(); ///动态规划求解 void getLCS(); ///采用倒推方式求最长公共子序列 int main() { int i; strcpy(a,"ABCBDAB"); strcpy(b,"BDCABA"); memset(num,0,sizeof(num)); memset(flag,0,sizeof(flag)); LCS(); printf("%d\n",num[strlen(a)][strlen(b)]);
getLCS(); return 0; } void LCS() { int i,j; for(i=1;i<=strlen(a);i++) { for(j=1;j<=strlen(b);j++) { if(a[i-1]==b[j-1]) ///注意这里的下标是i-1与j-1 { num[i][j]=num[i-1][j-1]+1; flag[i][j]=1; ///斜向下标记 } else if(num[i][j-1]>num[i-1][j]) { num[i][j]=num[i][j-1]; flag[i][j]=2; ///向右标记 } else { num[i][j]=num[i-1][j]; flag[i][j]=3; ///向下标记 } } } }
void getLCS() { char res[500]; int i=strlen(a); int j=strlen(b); int k=0; ///用于保存结果的数组标志位 while(i>0 && j>0) { if(flag[i][j]==1) ///如果是斜向下标记 { res[k]=a[i-1]; k++; i--; j--; } else if(flag[i][j]==2) ///如果是斜向右标记 j--; else if(flag[i][j]==3) ///如果是斜向下标记 i--; } for(i=k-1;i>=0;i--) printf("%c",res[i]); }
#include<stdio.h> #include<string.h> char a[30],b[30]; int lena,lenb; int LCS(int,int); ///两个参数分别表示数组a的下标和数组b的下标 int main() { strcpy(a,"ABCBDAB"); strcpy(b,"BDCABA"); lena=strlen(a); lenb=strlen(b); printf("%d\n",LCS(0,0)); return 0; } int LCS(int i,int j) { if(i>=lena || j>=lenb) return 0; if(a[i]==b[j]) return 1+LCS(i+1,j+1); else return LCS(i+1,j)>LCS(i,j+1)? LCS(i+1,j):LCS(i,j+1); }
对于字符串相似度寻找,
将字符串想象成下面的结构。
A处 是一个标记,为了方便讲解,不是这个表的内容。
| abc | a | b | c |
abe | 0 | 1 | 2 | 3 |
a | 1 | A处 |
|
|
b | 2 |
|
|
|
e | 3 |
|
|
|
c.来计算A处 出得值
它的值取决于:左边的1、上边的1、左上角的0.
按照Levenshtein distance的意思:
上面的值和左面的值都要求加1,这样得到1+1=2。
A处 由于是两个a相同,左上角的值加0.这样得到0+0=0。
这是后有三个值,左边的计算后为2,上边的计算后为2,左上角的计算为0,所以A处 取他们里面最小的0.
首先我们明确从一个字符串变化到另一个字符串需要进行添加、修改、删除来变化
如a变化到ab需要一步,添加一个b,
aa变化到ab需要修改一个a到b,
ab变化到a需要删除一个b。
首先我们确定了两个字符串str1,str2;假设这两个字符为a1a2a3a4......,b1b2b3......
那么构建一个二维矩阵
空 a1a2a3a4 ......
空 [1] [2] [3] [4] [5]......
b1 [6] [7] [8] [9] [10]......
b2 [11] [12][13][14] [15]......
b3 [16][17] .......
...
1.判断[1]左边为空,上面为空,从空到空需要变化0次
2.所以可以得到下面的矩阵
空 a1a2a3a4 ......
空 0 1 2 3 4......
b1 1 [7] [8] [9] [10]......
b2 2 [12][13][14] [15]......
b3 3 [17] .......
.......
3.到7的位置表示了[空a1]变化到[空b1],这里我们需要得到三个值
1)从[2]变化到[7]需要的步数是[2]+1
2)从[6]变化到[7]需要的变化是[6]+1
3) 从[1]变化到[7]需要的变化是 ,如果a1=b1,那么需要0步,如果a1!=b1,那么需要删除一个a1在添加一个b1,需要2步,也就是大于1步。
我们取这三步中所需走的最短步数填到[7]的位置 。
4.以此推得到
Amn的值为Am-1n+1,Amn-1+1,Am-1n-1+x(当am=bn时x=0,否则x=2)的最小值
5.当求得的值的最后一位得到的值N,用1-n/(max(len(a),len(b)))得到相关度。
#include<stdio.h>
#include<string.h>char *a="abcgh";char *b="aecdgh";int min(int t1,int t2,int t3) ///求三个数的最小值{
int min;
min=t1<t2?t1:t2;
min=min<t3?min:t3;
return min;
}int calculate(int i,int enda,int j,int endb)
{
int t1,t2,t3;
if(i>enda) ///i指示超过a[]的范围时 {
if(j>endb)
return 0;
else
return endb-j+1;
}
if(j>endb) ///j指示超过b[]的范围时 {
if(i>enda)
return 0;
else
return enda-i+1;
}
if(*(a+i) == *(b+j)) ///如果两个相等,则直接求下一个位置
return calculate(i+1,enda,j+1,endb);
else
{
t1=calculate(i+1,enda,j,endb); ///删除a[i]或在b中插入a[i]
t2=calculate(i,enda,j+1,endb); ///删除b[j]或在a中插入b[j]
t3=calculate(i+1,enda,j+1,endb); ///替换
return 1+min(t1,t2,t3);
}
}int main()
{
int dis=calculate(0,strlen(a)-1,0,strlen(b)-1);
printf("dis=%d",dis);
return 1;
}
4.用动态规划求解代码
#include<stdio.h>
#include<string.h>#define MAX 1000int dp[MAX][MAX]; ///dp[i][j]表示当前a[0..i-1]与b[0..j-1]的编辑距离char *a="agbgd";char *b="ggd";
int min(int t1,int t2,int t3) ///求三个数的最小值{
int min;
min=t1<t2?t1:t2;
min=min<t3?min:t3;
return min;
}
int main()
{
int i,j;
int lena=strlen(a),lenb=strlen(b);
memset(dp,0,sizeof(dp));
for(i=0;i<=lena;i++) ///a作为行,当b为空串时
dp[0][i]=i;
for(i=0;i<=lenb;i++) ///b作为列,当a为空串时
dp[i][0]=i;
for(i=1;i<=lena;i++)
{
for(j=1;j<=lenb;j++)
{
if(*(a+i)==*(b+j)) ///相等时
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=1+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]); ///不相等时,取三种可能操作的最小数值+1 }
}
printf("编辑距离为:dis=%d\n",dp[lena][lenb]);
return ;
}
6.