zoukankan      html  css  js  c++  java
  • 最长公共子序列 动态规划类

    最长公共子序列


    目录


    接下来我们来学习一下最长公共序列。

    定义

    最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是:一个序列S,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则S称为已知序列的最长公共子序列。

    问题引进

    首先我们引进一个问题:在一个计算机词典中,用户想要找一个单词,fish,但是用户不小心把单词拼写错误了,拼出了hish......这可怎么办。词典中当然没有hish这个单词,但是我们有fish,finish等单词,我们是不是应该去找这个单词呢?

    wordcompare

    对于这个问题,我们该.....怎么解决呢???

    ------>这是返回<------


    思想构成

    简述思想
    • 对于这个问题.....我们不妨,可以通过比较他们的字符串,从而得到用户所需要的一个单词。就好比如说,fish,finish这两个单词都有ish这个字段,但用户输入的单词(hish)长度为4,那么....我们不可能去匹配finish这个单词,因为超出了用户拼写单词的最长长度.....
    • 简而言之.....我们得从fish这个单词中匹配我们需要的单词。接下来.....便是怎么匹配的问题了。

    notephoto

    匹配思想
    • 对于这个我们刚刚的简述的内容,我们边可以进行匹配了......(这是废话....)
    • 匹配的思路大概是这样的:

    我们可以从第一个字符开始比,然后比对到最后一个字符。一一进行比较,一直到最后我们的所有的字符都完成,最后统计哪些字符串是比较接近的,然后我们进行输出....

    • 对于上面的这个思路....只能说,可以实行但是复杂度很高,基本上接近了O( n2 )...我们是否有另一种思路去做这个问题?

    ------>这是返回<------


    动态规划

    了解一下动态规划:

    • 动态规划可以帮助你在给定的约束条件下找到最优解。在背包问题中,我们可以在给定的背包容量的情况下,偷到价值最高的商品。
    • 在问题可分解为彼此独立且离散的子问题,那么我们可以想一下这个大的问题能不能使用动态规划去解决。

    dynamicdivide

    我们再来学习一些小tips:

    • 每种动态规划解决方案都涉及网格。
    • 单元格中的值通常就是你要优化的值。
    • 每个单元格都是一个子问题,因此你应该考虑如何将问题分成子问题,这有助于找出网络的坐标轴。

    根据刚刚所说的一些动态规划的思想和tips,我们如果需要利用动态规划的话,我们得把这个问题分解成若干个子问题....

    就好比如,我们找fish,输入了hish,那么我们得比较hfish的匹配程度,然后还得分别比较i,s,h这几个,最后在进行合并,那么我们就可以求得fishhish单词的匹配度。

    这就是一个巫术......很难,我们想不出任何的思路。为什么不用费曼算法来解决呢?步骤如下:

    • 将问题写下来
    • 好好思考
    • 将答案写下来

    magic

    哈哈,不逗了,接下来我们来解决LCS(最长公共子序列)问题

    ------>这是返回<------


    解决问题

    为了解决LCS问题,我们需要把原问题分解成若干个子问题,所以需要刻画LCS的特征。

    • 设A="a0,a1,...,am",B="b0,b1,...,bn",且Z="z0,z1,...,zk",为他们的最长公共子序列。他们有如下的性质
    • 如果am=bn,则zk=am=bn,且"z0,z1,...,z(k-1)"是"a0,a1,...,a(m-1)"和"b0,b1,...,b(n-1)"的一个最长公共子序列
    • 如果am!=bn,则若zk!=am,蕴含"z0,z1,...,zk"是"a0,a1,...,a(m-1)"和"b0,b1,...,bn"的一个最长公共子序列
    • 如果am!=bn,则若zk!=bn,蕴含"z0,z1,...,zk"是"a0,a1,...,am"和"b0,b1,...,b(n-1)"的一个最长公共子序列

    进行一系列的递归以及代入操作,我们得到了以下的递归公式。我们使用一个矩阵来进行运算......

    		 | 0               			i==0||j==0
    c[ i, j ]| c[i-1][j-1]+1        	i,j>0&&xi==yi
    		 | max{c[i][j-1],c[i-1][j]} i,j>0&&xi!=yi
    

    请看图示:

    photo1

    photo2

    photo3

    photo4

    好了,以上便是最长字段的讲解了.......

    在完成这个存储数的矩阵之后,最后一个元素的值便是我们需要求的的字符串的长度。这是运行图:

    run

    ------>这是返回<------


    接下来便是代码部分

    
    #include<iostream>
    #include<vector>
    #include<cstdlib>
    #include<string>
    #include<stack>
    
    using namespace std;
    
    string MaxSegments(string& str1, string& str2);
    string Process(int a, int b, string& str1, string& str2);
    void Display(vector<vector<int>>& pos);
    void Display(vector<vector<string>>& pos);
    
    int main()
    {
    
    	string str1, str2, pos;
    	cout << "请输入两个字符串" << endl;
    	cin >> str1 >> str2;
    	pos=MaxSegments(str1, str2);
    	cout << "最大字段是: " << pos << endl;
    	cout << "长度是:" << pos.size() << endl;
    
    	return 0;
    }
    
    string MaxSegments(string &str1,string &str2)
    {
    	//根据大小来开放空间
    	int a = str1.size();
    	int b = str2.size();
    	string pos;
    	if (a > b)//当传进来的字符串1的长度比第二个长
    	{
    		pos = Process(a, b, str1, str2);
    	}
    	else//比第二个短
    	{
    		pos = Process(b, a, str2, str1);
    	}
    	return pos;
    }
    
    string Process(int a, int b, string& str1, string& str2)
    {
    	vector<vector<int>>Pair(a+1, vector<int>(b+1, 0));//用来存储最大长度
    	vector<vector<string>>Graph(a+1, vector<string>(b+1, "0"));//用来存储走的路径
    	stack<char>ans;
    	string pos;
    	for (int i = 0; i < a+1; i++)
    	{
    		for (int j = 0; j < b+1; j++)
    		{
    			if (i == 0 || j == 0)//这是条件方程
    			{
    				Pair[i][j] = 0;
    				Graph[i][j] = "0";
    			}
    			else if ((i > 0 || j > 0) && (str1[i-1] == str2[j-1]))
    			{
    				Pair[i][j] = Pair[i - 1][j - 1] + 1;
    				Graph[i][j] = "↖";
    			}
    			else
    			{
    				if (Pair[i - 1][j] > Pair[i][j - 1])
    				{
    					Pair[i][j] = Pair[i - 1][j];
    					Graph[i][j] = "↑";
    				}
    				else
    				{
    					Pair[i][j] = Pair[i][j - 1];
    					Graph[i][j] = "←";
    				}
    			}
    		}
    	}//经过之后,可以找到最长字串的个数了
    	
    	//Display(Pair);//测试的时候显示路径
    	//Display(Graph);
    	
    	while (Graph[a][b] != "0")
    	{
    		if (Graph[a][b] == "↖")
    		{
    			ans.push(str1[a-1]);
    			a--, b--;
    		}
    		else if (Graph[a][b] == "↑")
    		{
    			a--;
    		}
    		else if (Graph[a][b] == "←")
    		{
    			b--;
    		}
    	} 
    	while (!ans.empty())
    	{
    		pos.push_back(ans.top());
    		ans.pop();
    	}
    	return pos;
    }
    
    void Display(vector<vector<int>>& pos)
    {
    	for (int i = 0; i < pos.size(); i++)
    	{
    		for (int j = 0; j < pos[0].size(); j++)
    		{
    			cout << pos[i][j] << " ";
    		}
    		cout << endl;
    	}
    }
    void Display(vector<vector<string>>& pos)
    {
    	for (int i = 0; i < pos.size(); i++)
    	{
    		for (int j = 0; j < pos[0].size(); j++)
    		{
    			cout << pos[i][j] << " ";
    		}
    		cout << endl;
    	}
    }
    
    

    ------>这是返回<------


    参考书籍《算法导论》、《算法图解》、百度百科

    这是小睿的博客,如果需要转载,请标注出处啦~ヾ(≧▽≦*)o谢谢。
  • 相关阅读:
    Memento模式
    CSS实现半透明div层的方法
    JS解析json数据(如何将json字符串转化为数组)
    并发容器Map之二:ConcurrentNavigableMap
    JSinArray检查数组中是否存在某个值
    Servlet3.0之七:@WebFilter申明该类为过滤器
    Spring源码阅览——BeanFactory体系结构
    使用 Selenium RC 测试 web 应用程序
    函数式编程
    9 个 Java 处理 Exception 的最佳实践
  • 原文地址:https://www.cnblogs.com/Yunrui-blogs/p/12104534.html
Copyright © 2011-2022 走看看