#include<iostream>
#include<cstring>
#include<stack>
#include<utility>
#define LEFTUP 0
#define LEFT 1
#define UP 2
using
namespace
std;
int
Max(
int
a,
int
b,
int
c,
int
*max){
//找最大者时a的优先级别最高,c的最低.最大值保存在*max中
int
res=0;
//res记录来自于哪个单元格
*max=a;
if
(b>*max){
*max=b;
res=1;
}
if
(c>*max){
*max=c;
res=2;
}
return
res;
}
//调用此函数时请注意把较长的字符串赋给str1,这主要是为了在回溯最长子序列时节省时间。如果没有把较长的字符串赋给str1不影响程序的正确执行。
string LCS(
const
string &str1,
const
string &str2){
int
xlen=str1.size();
//横向长度
int
ylen=str2.size();
//纵向长度
if
(xlen==0||ylen==0)
//str1和str2中只要有一个为空,则返回空
return
""
;
pair<
int
,
int
> arr[ylen+1][xlen+1];
//构造pair二维数组,first记录数据,second记录来源
for
(
int
i=0;i<=xlen;i++)
//首行清0
arr[0][i].first=0;
for
(
int
j=0;j<=ylen;j++)
//首列清0
arr[j][0].first=0;
for
(
int
i=1;i<=ylen;i++){
char
s=str2.at(i-1);
for
(
int
j=1;j<=xlen;j++){
int
leftup=arr[i-1][j-1].first;
int
left=arr[i][j-1].first;
int
up=arr[i-1][j].first;
if
(str1.at(j-1)==s)
//C1==C2
leftup++;
int
max;
arr[i][j].second=Max(leftup,left,up,&arr[i][j].first);
// cout<<arr[i][j].first<<arr[i][j].second<<" ";
}
// cout<<endl;
}
/*矩阵构造完毕*/
//回溯找出最长公共子序列
stack<
int
> st;
int
i=ylen,j=xlen;
while
(!(i==0&&j==0)){
if
(arr[i][j].second==LEFTUP){
if
(arr[i][j].first==arr[i-1][j-1].first+1)
st.push(i);
--i;
--j;
}
else
if
(arr[i][j].second==LEFT){
--j;
}
else
if
(arr[i][j].second==UP){
--i;
}
}
string res=
""
;
while
(!st.empty()){
int
index=st.top()-1;
res.append(str2.substr(index,1));
st.pop();
}
return
res;
}
int
main(){
string str1=
"GCCCTAGCG"
;
string str2=
"GCGCAATG"
;
string lcs=LCS(str1,str2);
cout<<lcs<<endl;
return
0;
}
|
扩展
对于一个字符串a可以通过增加一个字符、删除一个字符、修改一个字符,将字符串a变成字符串b,例如
a= abcddefg
b = abcefg
可以通过a字符串删除两个dd得到b字符串,也可以通过b字符串增加dd编程a字符串,从上面的分析可以知道,增加和删除的代价必须是相同的,这样a字符串变成b字符串的代价和b字符串变成a字符串的代价才会是相同的,否这可能产生代价不对称的情况。其实我们可以设定修改和增加(删除)的代价是不同的,当然也可以认为他们是一样的。
实际的计算过程可以如下进行:
1)比较a[i]和b[j];
2)如果a[i] == b[j],那么distance = EditDistance(a[i + i], b[j + 1]) + 0;
3)如果a[i] != b[j],那么可以经过如下操作使得a[i]等于b[j]
a) a[i]前增加b[j],那么distance = EditDistance(a[i], b[j + 1] + insert_cost
b)b[j]前增加a[i],那么distance = EditDistance(a[i + 1], b[j]) + insert_cost
c)删除a[i],那么distance = EditDistance(a[i + 1], b[j]) + delete_cost
d)删除b[j],那么distance = EditDistance(a[i], b[j + 1] + delete_cost
e)a[i]变成b[j],那么distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
f)b[j]变成a[i],那么distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
如果insert_cost == delete_cost,那么a添加字符变成b和b删除字符变成a是等价的,a[i]变成b[j]与b[j]变成a[i]也是等价的,因此实际需要考虑的代价就是下面3种情况:
i) distance = EditDistance(a[i], b[j + 1] + insert_cost(或delete_cost)
ii) distance = EditDistance(a[i + 1], b[j] + insert_cost(或delete_cost)
iii)distance = EditDistance(a[i + 1], b[j + 1] + replace_cost
如果我们回顾一下最长公共子序列问题(LCS),就会发现这个问题和LCS问题几乎是等价的。因为可以这样理解,找出a和b的LCS,保持LCS对齐不变,增加删除一些字符就完成了变换,而这样的代价应该是最小的(猜测的,没有证明)
这样一个问题,我们可以使用递归来解决。
当然的解决方案是用动态规划的方法解决,采用动态规划解决时,我们假设前面字符串都已经变换相同了,那么在a[i]变成b[j]的过程中需要对比如下代价:
1)如果a[i] == b[j],那么前面的状态可能是a[i - 1] b[j - 1]或者 a[i - 1] b[j]或者a[i] b[j - 1],我们要比较这些可能的转换过程中哪个代价更小;
2)如果a[i] != b[j],那么:
i)a[i] b[j]可能从a[i - 1] b[j - 1]状态通过replace a[i] to a[j] + replace的代价来实现;
ii)a[i] b[j]可能从a[i ] b[j - 1]状态通过为a[i]前面添加一个b[j -1] + insert的代价来实现;
iii)a[i] b[j]可能从a[i - 1] b[j]状态通过删除a[i - 1] + delete的代价来实现;
而这些代价就通过一个向量cost向量来存储。
程序代码如下:
- #include <stdio.h>
- #include <string>
- int Min(int a, int b, int c) {
- int tmp = a > b ? b : a;
- tmp = tmp > c ? c : tmp;
- return tmp;
- }
- int EditDistance(const std::string& a, int a_offset, const std::string& b, int b_offset) {
- if (a_offset == a.size() && b_offset < b.size()) {
- return EditDistance(a, a_offset, b, b_offset + 1) + 1 ;
- } else if (b_offset == b.size() && a_offset < a.size()) {
- return EditDistance(a, a_offset + 1, b, b_offset) + 1;
- } else if (a_offset == a.size() && b_offset == b.size()) {
- return 0;
- } else {
- if (a[a_offset] == b[b_offset]) {
- return EditDistance(a, a_offset + 1, b, b_offset + 1);
- } else {
- int distance1 = EditDistance(a, a_offset + 1, b, b_offset) + 1;
- int distance2 = EditDistance(a, a_offset, b, b_offset + 1) + 1;
- int distance3 = EditDistance(a, a_offset + 1, b, b_offset + 1) + 1;
- return Min(distance1, distance2, distance3);
- }
- }
- }
- int EditDistance_DP(const std::string& a, const std::string& b) {
- int** cost = new int*[a.size() + 1];
- for (int i = 0; i < a.size() + 1; ++i) {
- cost[i] = new int[b.size() + 1];
- }
- for (int i = 0; i < a.size() + 1; ++i) {
- for (int j = 0; j < b.size() + 1; ++j) {
- cost[i][j] = 0;
- }
- }
- for (int i = 0; i < a.size(); ++i) {
- for (int j = 0; j < b.size(); ++j) {
- if (a[i] == b[j]) {
- cost[i + 1][j + 1] = Min(cost[i][j], cost[i][j + 1], cost[i + 1][j]);
- } else {
- cost[i + 1][j + 1] = Min(cost[i][j] + 1, cost[i][j + 1] + 1, cost[i + 1][j] + 1);
- }
- }
- }
- for (int i = 0; i <= a.size(); ++i) {
- for (int j = 0; j <= b.size(); ++j) {
- printf("%d ", cost[i][j]);
- }
- printf("\n");
- }
- int distance = cost[a.size()][b.size()];
- for (int i = 0; i < a.size() + 1; ++i) {
- delete[] cost[i];
- }
- delete[] cost;
- return distance;
- }
- int main(int argc, char** argv) {
- std::string a = "adefk";
- std::string b = "bdefg";
- printf("edit distance = %d\n", EditDistance(a, 0, b, 0));
- printf("edit distance = %d\n", EditDistance_DP(a, b));
- }