1、算法LCS所有的最长公共子序列 所有的最长公共子序列(LCS)一、 问题描述子序列的概念: 设X = ,若有1i1i2 ikm,得Z= = ,则称Z是X的子序列,记为ZX。e.g. X=, Z=, 则有ZX。公共子序列的概念:设X,Y是两个序列,且有ZX和ZY,则称Z是X和Y 的公共列。最长公共子序列的概念:若ZX,ZY,且不存在比Z更长的X和Y 的公共子序列,则称Z是X和Y 的最长公共子序列,记为Z LCS(X , Y)。但是LCS不是只有一个,最长公共子序列往往不止一个。e.g. X=, Y=, 则Z=, Z=, Z=均属于LCS(X , Y) ,即X,Y有3个LCS。本文描述如何寻找所有
2、的LCS二、问题分析先描述寻找一个LCS的思想:记Xi=x1,xi即X序列的前i个字符 (1im)(前缀)Yj=y1,yj即Y序列的前j个字符 (1jn)(前缀)假定Z=z1,zkLCS(X , Y)。若xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn。且显然有Zk-1LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。若xmyn,则亦不难用反证法证明:要么ZLCS(Xm-1, Y),要么ZLCS(X , Yn-1)。由于zkxm与zkyn其中至少有一个必成立
3、,因此:若zkxm则有ZLCS(Xm-1 , Y),若zkyn 则有ZLCS(X , Yn-1)。 若xm=yn,则问题化归成求Xm-1与Yn-1的LCS,(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1) 若xmyn,则问题化归成求Xm-1与Y的LCS及X与Yn-1的LCSLCS(X , Y)的长度为:Max LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度,因而具有重叠性。此外,两个序列的LCS中包含了两个
4、序列的前缀的LCS,故问题具有最优子结构性质 考虑用动态规划法。引进一个二维数组C,用Ci,j记录Xi与Yj的LCS的长度。 如果我们是按行、列的序号从小到大地进行递推计算,(从第1行开始计算:C1,1、C1,2、。C1,n,再算C2,1、C2,2、。C2,n,。最后计算Cm,1、Cm,2、。Cm,n,最后算出的Cm,n即为LCS(X , Y)的长度。)那么在计算Ci,j之前,Ci-1,j-1, Ci-1,j与Ci,j-1均已计算出来。此时根据Xi=Yj还是Xi Yj,就可以计算出Ci,j:若Xi=Yj,则执行Ci,jCi-1,j-1+1;若Xi Yj,进行下述判断:若Ci-1,jCi,j-1
5、则Ci,j取Ci-1,j;否则Ci,j取Ci,j-1。即有Ci,j=为了构造出LCS,使用一个m n的二维数组b,bi,j记录Ci,j是通过哪一个子问题的值求得的,以决定搜索的方向:若Xi=Yj,则bi,j中记入“”(亦可不记);若Xi Yj且Ci-1,j Ci,j-1,则bi,j中记入“”;若Xi Yj且Ci-1,j Ci,j-1,则bi,j中记入“”;e.g. 对于X=,Y=,求出的各个Ci,j与bi,j如下图: 0 1 2 3 4 5 6 yj B D C A B A0 xi 0 0 0 0 0 0 0 1 A 0 0 0 0 11 1 2 B 0 111 1 22 3 C 0 1 1
6、2 2 2 2 4 B 0 1 1 2 2 33 5 D 0 1 2 2 2 3 3 6 A 0 1 2 2 3 3 4 7 B 0 1 2 2 3 4 4找出所有路径的思想:仅用“” ,“” ,“”是搜索不到所有的LCS的,因为Ci-1,jCi,j-1,我们没有区分Ci-1,jCi,j-1还是Ci-1,jCi,j-1此时我们只是在单方向搜索,就像是图的深度优先搜索,走到底,找出一条路径。为了找出所有的LCS,我们将Ci-1,jCi,j-1记做“”。同时用遍历bi,j构造出一棵树tree,“”的方向记做节点的左子树,右子树为空,“”的方向记做节点的右子树,左子树为空,“”的方向开辟新的节点,并
7、对其赋值,“”记做节点的左子树和右子树。当树构造完毕时,我们从叶子节点开始遍历,一直到根为止,即找出所有的LCS。注意:此时找出的所有的LCS可能有重复的,所以用一个字符串数组来记录不同的LCS。容易证明该字符数组最长为minx.length,y.length;三、解决方案 为了方便,程序中将“” 记做1,“”记做-1 ,“”记做0,“”记做2./treenode.h#ifndef TREENODE_H#define TREENODE_Hclass TreeNode friend class tree;public: TreeNode(char a=0) /构造函数 data=a; leftc
8、hild=0; rightchild=0; parent=0; TreeNode * leftchild; TreeNode * rightchild; TreeNode* parent; /从叶子往根遍历时找的路线 char data; ;#endif/构造树/tree.h#ifndef TREE_H#define TREE_H#include treenode.h#include #include stack.hconst int m=7,n=6; /默认x的长度为7,y的长度为6int i=0,j=0;int exsit=0; /记录字符串数组有几个元素class treepublic:
9、 tree(int bm+1n+1,string x,int i,int j); TreeNode* LCS(int bm+1n+1,string x,int i,int j);/构造树 void inorder(); void inorder(TreeNode*);/中序遍历,找出所有的叶子节点 void con_parent();/遍历树,找出每个节点的parent void tranverse(TreeNode*);/从叶子节点遍历到根,找出LCS void output(); /输出所有的LCSprivate: TreeNode *root;/根 Stack stack;/用栈来记录叶
10、子节点 string *t;/字符串数组记录不同的LCS;tree:tree(int bm+1n+1,string x,int i,int j) t=new stringn;/字符串数组最长为minx.length,y.length for (int y=0;yleftchild=LCS(b,x,i-1,j-1);/左子树继续构造 return a; else if (bij=1) return LCS(b,x,i-1,j);/往上面走,不创造新节点,继续递归 else if (bij=-1) return LCS(b,x,i,j-1);/往左面走,不创造新节点,继续递归 else /遇到两个
11、方向的点,创造新节点,并默认赋值为#,递归构造 左子树和右子树。 TreeNode* a=new TreeNode(#); a-leftchild=LCS(b,x,i-1,j); a-rightchild=LCS(b,x,i,j-1); return a; void tree:inorder() inorder(root);/找出所有的叶子节点用栈来记录void tree:inorder(TreeNode* current) if(current) inorder(current-leftchild); if(current-data=#) stack.add(current); inorde
12、r(current-rightchild); 遍历树,找出每个节点的parentvoid tree:con_parent() int i=0; Stack s; TreeNode* currentNode=root; while(1) while(currentNode) s.add(currentNode); TreeNode* pp=currentNode; if (currentNode-leftchild) currentNode-leftchild-parent=pp; currentNode=currentNode-leftchild; if (s.IsEmpty() return
13、; currentNode=s.Top(); coutdatarightchild) currentNode-rightchild-parent=pp; currentNode=currentNode-rightchild; /从叶子遍历到根,找出LCSvoid tree:tranverse(TreeNode* leaf) TreeNode* currentNode=leaf; string temp=; bool flag=true; while(currentNode-parent) if (currentNode-data!=#¤tNode-data!=$) temp=tem
14、p+currentNode-data+ ; currentNode=currentNode-parent; if(root-data!=#)/若根有非真值添加到其中去 temp+=root-data; /看LCS若有重复的,不存入string数组中, for (int count=0;countm;count+) if (temp=tcount) flag=false; break; if (flag) couttempn; texsit+=temp; temp=; flag=true;/找出所有的LCSvoid tree:output() while(!stack.IsEmpty() tra
15、nverse(stack.Top(); #endif/test.cpp 测试函数#include using namespace std;#include tree.hint main() int bm+1n+1; int cm+1n+1; string x= abcdefg; string y= fedcba; int i,j; for(i=0;i=m;i+) ci0=0; bi0=0; for (j=1;j=n;j+) c0j=0; b0j=0; /构造出cij,bij for (i=1;i=m;i+) for (j=1;jcij-1) cij=ci-1j; bij=1; else if (ci-1jcij-1) cij=cij-1; bij=-1; else cij=ci-1j; bij=2; tree t(b,x,7,6); /构造树 t.con_parent();/找出每个节点的parent t.inorder();/找出所有的叶子节点 t.output();/输出所有的LCS return 0;四、输出结果X=, Y=X= y= x=,y=五、性能分析显然,构造二维数组b,c时间为;构造树时间,T(n)=2T(n-1)时间为O;找出parent节点的时间为O(logn),从叶子遍历到根时间为O(logn);该算法复杂度为O.性能不好。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1