`
phinecos
  • 浏览: 341171 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

[转]最长递增子序列问题的求解

 
阅读更多

最长递增子序列问题是一个很基本、较常见的小问题,但这个问题的求解方法却并不那么显而易见,需要较深入的思考和较好的算法素养才能得出良好的算法。由于这个问题能运用学过的基本的算法分析和设计的方法与思想,能够锻炼设计较复杂算法的思维,我对这个问题进行了较深入的分析思考,得出了几种复杂度不同算法,并给出了分析和证明。<?xml:namespace prefix = o />

一, 最长递增子序列问题的描述<?xml:namespace prefix = u1 />

L=<a1,a2,…,an>n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<kmaK1<ak2<…<akm。求最大的m值。

二, 第一种算法:转化为LCS问题求解

设序列X=<b1,b2,…,bn>是对序列L=<a1,a2,…,an>按递增排好序的序列。那么显然XL的最长公共子序列即为L的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。

最长公共子序列问题用动态规划的算法可解。设Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它们分别为LX的子序列。令C[i,j]LiXj的最长公共子序列的长度。这可以用时间复杂度为O(n2)的算法求解,由于这个算法上课时讲过,所以具体代码在此略去。求最长递增子序列的算法时间复杂度由排序所用的O(nlogn)的时间加上求LCSO(n2)的时间,算法的最坏时间复杂度为O(nlogn)O(n2)O(n2)

三, 第二种算法:动态规划法

<wrapblock><?xml:namespace prefix = u4 /><shape id="_x0000_s1027" style="MARGIN-TOP: 49.2pt; Z-INDEX: 2; LEFT: 0px; MARGIN-LEFT: 108pt; WIDTH: 211.65pt; POSITION: absolute; HEIGHT: 54.3pt; TEXT-ALIGN: left" type="#_x0000_t75"><span style="FONT-SIZE: 10pt; mso-ascii-font-family: Verdana"><imagedata src="file:///C:/DOCUME~1/jl/LOCALS~1/Temp/msoclip1/01/clip_image003.wmz" u1:title=""></imagedata><?xml:namespace prefix = u5 /><wrap type="topAndBottom"></wrap></span></shape></wrapblock>设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:

这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<iaj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。

这个算法由Java实现的代码如下:

publicvoidlis(float[]L)
{
intn=L.length;
int[]f=newint[n];//用于存放f(i)值;
f[0]=1;//以第a1为末元素的最长递增子序列长度为1;
for(inti=1;i<n;i++)//循环n-1次
{
f[i]
=1;//f[i]的最小值为1;
for(intj=0;j<i;j++)//循环i次
{
if(L[j]<L[i]&&f[j]>f[i]-1)
f[i]
=f[j]+1;//更新f[i]的值。
}

}

System.out.println(f[n
-1]);
}



这个算法有两层循环,外层循环次数为n-1次,内层循环次数为i次,算法的时间复杂度

所以T(n)=O(n2)。这个算法的最坏时间复杂度与第一种算法的阶是相同的。但这个算法没有排序的时间,所以时间复杂度要优于第一种算法。

一, 对第二种算法的改进

在第二种算法中,在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j),如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储子序列的最大递增子序列的最末元素,即有

B[f(j)] = aj

在计算f(i)时,在数组B中用二分查找法找到满足j<iB[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。下面先写出代码,再证明算法的证明性。用Java实现的代码如下:

lis1(float[]L)
{
intn=L.length;
float[]B=newfloat[n+1];//数组B;
B[0]=-10000;//把B[0]设为最小,假设任何输入都大于-10000;
B[1]=L[0];//初始时,最大递增子序列长度为1的最末元素为a1
intLen=1;//Len为当前最大递增子序列长度,初始化为1;
intp,r,m;//p,r,m分别为二分查找的上界,下界和中点;
for(inti=1;i<n;i++)
{
p
=0;r=Len;
while(p<=r)//二分查找最末元素小于ai+1的长度最大的最大递增子序列;
{
m
=(p+r)/2;
if(B[m]<L[i])p=m+1;
elser=m-1;
}

B[p]
=L[i];//将长度为p的最大递增子序列的当前最末元素置为ai+1;
if(p>Len)Len++;//更新当前最大递增子序列长度;


}

System.out.println(Len);
}

现在来证明这个算法为什么是正确的。要使算法正确只须证如下命题:

命题1每一次循环结束数组B中元素总是按递增顺序排列的。

证明:用数学归纳法,对循环次数i进行归纳。

i=0时,即程序还没进入循环时,命题显然成立。

i<k时命题成立,当i=k时,假设存在j1<j2,B[j1]>B[j2],因为第i次循环之前数组B是递增的,因此第i次循环时B[j1]B[j2]必有一个更新,假设B[j1]被更新为元素ai+1,由于ai+1=B[j1]> B[j2],按算法ai+1应更新B[j2]才对,因此产生矛盾;假设B[j2]被更新,设更新前的元素为s,更新后的元素为ai+1,则由算法可知第i次循环前有B[j2]s< ai+1< B[j1],这与归纳假设矛盾。命题得证。

命题2B[c]中存储的元素是当前所有最长递增子序列长度为c的序列中,最小的最末元素,即设当前循环次数为i,有B[c]={aj|<?xml:namespace prefix = u8 /><shape id="_x0000_i1025" style="WIDTH: 12pt; HEIGHT: 12.75pt" type="#_x0000_t75" u1:ole=""></shape><imagedata src="file:///C:/DOCUME~1/jl/LOCALS~1/Temp/msoclip1/01/clip_image007.wmz" u1:title=""></imagedata>f(k)=f(j)=ck,ji+1ajak}(f(i)为与第二种算法中的f(i)含义相同)

证明:程序中每次用元素ai更新B[c](c=f(i)),设B[c]原来的值为s,则必有ai<s,不然ai就能接在s的后面形成长度为c+1的最长递增子序列,而更新B[c+1]而不是B[c]了。所有B[c]中存放的总是当前长度为c的最长递增子序列中,最小的最末元素。

命题3设第i次循环后得到的pp(i+1),那么p(i)为以元素ai为最末元素的最长递增子序列的长度。

证明:只须证p(i)等于第二种算法中的f(i)。显然一定有p(i)<f(i)。假设p(i)<f(i),那么有两种情况,第一种情况是由二分查找法找到的p(i)不是数组B中能让ai接在后面成为新的最长递增子序列的最大的元素,由命题1和二分查找的方法可知,这是不可能的;第二种情况是能让ai接在后面形成长于p(i)的最长递增子序列的元素不在数组B中,由命题2可知,这是不可能的,因为B[c]中存放的是最末元素最小的长度为c的最长递增子序列的最末元素,若ai能接在长度为L(L> p(i))的最长递增子序列后面,就应该能接在B[L]后面,那么就应该有p(i)=L,L> p(i)矛盾。因此一定有p(i)f(i),命题得证。

算法的循环次数为n,每次循环二分查找用时logn,所以算法的时间复杂度为O(nlogn)。这个算法在第二种算法的基础上得到了较好的改进。

五, 总结

本论文只给出了计算解的大小而没有给出构造解的方法,因为我认为计算解的大小的算法已能给出对问题的本质认识,只要计算解大小的算法设计出,构造解就只是技术细节的问题了,而我关心的是怎样对问题得到很好的认识而设计出良好的算法。以上几种算法已用Java实现,都能得到正确的结果。在设计和改进算法时用到了基本的算法设计和分析、证明的基本方法,很好的锻炼了设计与分析算法的思维能力,让我从感性上认识到算法分析与设计的重要性,并且感受了算法分析、设计和改进的乐趣。

分享到:
评论

相关推荐

    求解最大子序列、最长递增子序列、最长公共子串、最长公共子序列

    求解最大子序列、最长递增子序列、最长公共子串、最长公共子序列. http://blog.csdn.net/ssuchange/article/details/17341693

    最长递增子序列

    用动态规划实现最长递增子序列的求解,并回溯输出最长公共子序列

    最长递增子序列的求法

    最长递增子序列问题是一个很基本、较常见的小问题,但这个问题的求解方法却并不那么显而易见,需要较深入的思考和较好的算法素养才能得出良好的算法。由于这个问题能运用学过的基本的算法分析和设计的方法与思想,...

    动态规划算法求解最长严格递增子序列的长度问题

    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。(用递归和动态规划算法分别解决并比较计算时间)例如: 输入:nums = [10,9,2,5,3,7,101,18] ...

    最长递增子序列 动态规划法.cpp.rar

    C++的课程作业,一个简单的程序,用dev就能直接运行,老师应该不会太仔细检查,糊弄一下肯定没事的,不过最好能自己看懂就是了

    最长公共子序列问题.docx

    字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=(x0,x1,…,xm-1),序列Y...该问题是求两序列A和B的最长公共子序列(LCS)。

    C语言实现最长递增子序列问题的解决方法

    本文实例展示了C语言实现最长递增子序列问题的解决方法。分享给大家供大家参考。具体方法如下: 问题描述: 给定一个序列,找出其最长递增子序列长度。 比如 输入 1 3 7 5 输出 3 算法解决思路: 利用动态规划的思想...

    最长公共子序列,分治法,算法C++

    序列Z=,C,D,B&gt;是序列X=,B,C,B,D,A,B&gt;的子序列,相应的递增下标序列为,3,5,7&gt;。 一般地,给定一个序列X=,x2,…,xm&gt;,则另一个序列Z=,z2,…,zk&gt;... 你的任务是:给定2个序列X、Y,求X和Y的最长公共子序列Z。

    用动态规划思想求解最长公共子串

    若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有...给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列

    第四章 动态规划——2、31

    第四章 动态规划【二、三】算法 设计与分析动态规划法求解最长递增子序列问题【问题描述】给定一个无序的整数序列a[0..n-1],求其中最长递增子序列的长度。例如

    动态规划,算法中的魔法师!

    动态规划适用于多种实际应用场景,例如最短路径问题、背包问题、字符串匹配问题、最长公共子序列问题以及最长递增子序列问题等。在实际应用中,动态规划可以大大提高算法的性能,并找到问题的最优解。 总的来说,...

    LCS.rar_LCS

    实现求解整数的递增子序列。给出一串整数,求解其最长递增子序列。

    动态规划 ppt演示

    由最长公共子序列问题的最优子结构性质可知,要找出X=, x2, …, xm&gt;和Y=, y2, …, yn&gt;的最长公共子序列,可按以下方式递归地进行:当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的...

    什么是动态规划,及其解决的问题.md

    动态规划 动态规划(Dynamic Programming,简称DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。...5. **序列问题**:如最长递增子序列、最长公共子

    论文研究-生物信息挖掘中LIS算法研究.pdf

    探讨了生物信息挖掘中ó模式子序列问题的一个特例,即最长递增子序列(LIS)问题。对于LIS问题,分别用LCS算法、动态规划、动态规划结合二分法进行求解,并分析了这三种算法的时间和空间复杂度,对其中两种算法进行了...

    JavaScript-HTML-css实现算法可视化

    2. 采用动态规划策略设计并实现算法,求解最长公共子序列问题。要求时间复杂性不超过O(m*n)。 三、贪心法 1.用贪心策略设计与实现一个贪心算法,求解背包问题。 2. 假设活动已经按照结束时间递增的次序排序。用贪心...

    LIS & LCS(动态规划)

    问题描述 东东有两个序列A和B。...这个题是基本的动态规划问题,LIS是最长上升子序列,LCS是最长公共子序列。 求解LIS就是设dp[i]为以当前元素结尾的最长上升序列,那么dp[i]=max(dp[j]∣j&lt;i&&a

    关于蓝桥杯C++案例题和模拟题的示例

    题目描述:有一个数组,求其中最长递增子序列的长度。 图论算法: 题目描述:给定一个有向图,判断是否存在环路。 搜索算法: 题目描述:在一个迷宫中找到从起点到终点的最短路径。 模拟题: 数学计算: 题目...

    C++实现动态规划的思想

    动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解...动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解

    算法引论:一种创造性方法.[美]Udi Manber(带详细书签).pdf

    6.11.1 最长递增序列 6.11.2 查找集合中两个最大的元素 6.11.3 计算多重集合的模 6.12 小结 第7章 图算法 7.1 引言 7.2 欧拉图 7.3 图的遍历 7.3.1 深度优先搜索 7.3.2 广度优先搜索 7.4 拓扑排序 7.5 ...

Global site tag (gtag.js) - Google Analytics