最长上升子序列算法解析

  1 LIS什么是最长递增子序列呢?
  2 问题描述如下:
  3   设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>  4  其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
  5 对于这个问题有以下几种解决思路:
  6    1、把a1,a2,...,an排序,假设得到a'1,a'2,...,a'n,然后求a的a'的最长公共子串,这样总的时间复杂度为o(nlg(n))+o(n^2)=o(n^2);
  7    2、动态规划的思路:
  8     另设一辅助数组b,定义b[n]表示以a[n]结尾的最长递增子序列的长度,则状态转移方程如下:
  9     b[k]=max(max(b[j]|a[j]<a[k],j<k)+1,1);
 10     这个状态转移方程解释如下:在a[k]前面找到满足a[j]<a[k]的最大b[j],然后把a[k]接在它的后面,
 11         可得到a[k]的最长递增子序列的长度,或者a[k]前面没有比它小的a[j],那么这时a[k]自成一序列,
 12         长度为1.最后整个数列的最长递增子序列即为max(b[k]   | 0<=k<=n-1);
 13     实现代码如下:
 14     
 15 #include <iostream>
 16 using namespace std;
 17 int main()
 18 {
 19        int i,j,n,a[100],b[100],max;
 20        while(cin>>n)
 21        {
 22               for(i=0;i<n;i++)
 23                      cin>>a[i];
 24               b[0]=1;//初始化,以a[0]结尾的最长递增子序列长度为1
 25               for(i=1;i<n;i++)
 26               {
 27                      b[i]=1;//b[i]最小值为1
 28                      for(j=0;j<i;j++)
 29                             if(a[i]>a[j]&&b[j]+1>b[i])
 30                                    b[i]=b[j]+1;
 31               }
 32               for(max=i=0;i<n;i++)//求出整个数列的最长递增子序列的长度
 33                      if(b[i]>max)
 34                             max=b[i];
 35               cout<<max<<endl;
 36        }
 37        return 0;
 38 }
 39 显然,这种方法的时间复杂度仍为o(n^2);
 40 3.对第二种思路的改进:
 41 第二种思路在状态转移时的复杂度为o(n),即在找a[k]前面满足a[j]<a[k]的最大b[j]时采用的是顺序查找的方法,复杂度为o(n).
 42 设想如果能把顺序查找改为折半查找,则状态转移时的复杂度为o(lg(n)),这个问题的总的复杂度就可以降到nlg(n).
 43 另定义一数组c,c中元素满足c[b[k]]=a[k],解释一下,即当递增子序列的长度为b[k]时子序列的末尾元素为c[b[k]]=a[k].
 44 先给出这种思路的代码,然后再对其做出解释。
 45     
 46 #include <iostream>
 47 using namespace std;
 48 int find(int *a,int len,int n)//若返回值为x,则a[x]>=n>a[x-1]//假设最大长度为n+1,所以按照长度二分,再和a[i]来比较,用法巧妙
 49 {
 50        int left=0,right=len,mid=(left+right)/2;
 51        while(left<=right)
 52        {
 53               if(n>a[mid]) left=mid+1;
 54               else if(n<a[mid]) right=mid-1;
 55               else return mid;//长度已经存在
 56               mid=(left+right)/2;
 57        }
 58        return left;
 59 }
 60 void fill(int *a,int n)
 61 {
 62        for(int i=0;i<=n;i++)
 63               a[i]=1000;
 64 }
 65 int main()
 66 {
 67        int max,i,j,n,a[100],b[100],c[100];
 68        while(cin>>n)
 69        {
 70               fill(c,n+1);
 71               for(i=0;i<n;i++)
 72                      cin>>a[i];
 73               c[0]=-1;//    …………………………………………1
 74               c[1]=a[0];//        ……………………………………2
 75               b[0]=1;//     …………………………………………3
 76               for(i=1;i<n;i++)//        ………………………………4
 77               {
 78                      j=find(c,n+1,a[i]);//   ……………………5//j相当于b[k]
 79                      c[j]=a[i];// ………………………………6//c[b[k]]=a[k];//j的大小决定了a[i]在c[]中的位置,所以c【】是递增的,可以用二分
 80                      b[i]=j;//……………………………………7
 81               }
 82               for(max=i=0;i<n;i++)//………………………………8
 83                      if(b[i]>max)
 84                             max=b[i];
 85               cout<<max<<endl;
 86        }
 87        return 0;
 88 }
 89     对于这段程序,我们可以用算法导论上的loop invariants来帮助理解.
 90     loop invariant: 1、每次循环结束后c都是单调递增的。(这一性质决定了可以用二分查找)
 91                            2、每次循环后,c[i]总是保存长度为i的递增子序列的最末的元素,若长度为i的递增子序
 92                                   列有多个,刚保存末尾元素最小的那个.(这一性质决定是第3条性质成立的前提)
 93                            3、每次循环完后,b[i]总是保存以a[i]结尾的最长递增子序列。
 94     initialization:    1、进入循环之前,c[0]=-1,c[1]=a[0],c的其他元素均为1000,c是单调递增的;
 95                            2、进入循环之前,c[1]=a[0],保存了长度为1时的递增序列的最末的元素,且此时长度为1
 96                                  的递增了序列只有一个,c[1]也是最小的;
 97                            3、进入循环之前,b[0]=1,此时以a[0]结尾的最长递增子序列的长度为1.
 98     maintenance:   1、若在第n次循环之前c是单调递增的,则第n次循环时,c的值只在第6行发生变化,而由
 99                                 c进入循环前单调递增及find函数的性质可知(见find的注释),
100                                  此时c[j+1]>c[j]>=a[i]>c[j-1],所以把c[j]的值更新为a[i]后,c[j+1]>c[j]>c[j-1]的性质仍然成
101                                 立,即c仍然是单调递增的;
102                            2、循环中,c的值只在第6行发生变化,由c[j]>=a[i]可知,c[j]更新为a[i]后,c[j]的值只会变
103                                   小不会变大,因为进入循环前c[j]的值是最小的,则循环中把c[j]更新为更小的a[i],当
104                                  然此时c[j]的值仍是最小的;
105                            3、循环中,b[i]的值在第7行发生了变化,因为有loop invariant的性质2,find函数返回值
106                                 为j有:c[j-1]<a[i]<=c[j],这说明c[j-1]是小于a[i]的,且以c[j-1]结尾的递增子序列有最大的
107                                长度,即为j-1,把a[i]接在c[j-1]后可得到以a[i]结尾的最长递增子序列,长度为(j-1)+1=j;
108     termination:       循环完后,i=n-1,b[0],b[1],...,b[n-1]的值均已求出,即以a[0],a[1],...,a[n-1]结尾的最长递
109                               增子序列的长度均已求出,再通过第8行的循环,即求出了整个数组的最长递增子序列。
110           仔细分析上面的代码可以发现,每次循环结束后,假设已经求出c[1],c[2],c[3],...,c[len]的值,
111           则此时最长递增子序列的长度为len,因此可以把上面的代码更加简化,即可以不需要数组b来辅助存储,第8行的循环也可以省略。
112     */
113 /*#include <iostream>
114 using namespace std;
115 int find(int *a,int len,int n)//修改后的二分查找,若返回值为x,则a[x]>=n
116 {
117        int left=0,right=len,mid=(left+right)/2;
118        while(left<=right)
119        {
120               if(n>a[mid]) left=mid+1;
121               else if(n<a[mid]) right=mid-1;
122               else return mid;
123               mid=(left+right)/2;
124        }
125        return left;
126 }
127 int main()
128 {
129        int n,a[100],c[100],i,j,len,b[100];//新开一变量len,用来储存每次循环结束后c中已经求出值的元素的最大下标
130        while(cin>>n)
131        {
132               for(i=0;i<n;i++)
133                      cin>>a[i];
134               b[0]=1;
135               c[0]=-1;
136               c[1]=a[0];
137               len=1;//此时只有c[1]求出来,最长递增子序列的长度为1.
138               for(i=1;i<n;i++)
139               {
140                      j=find(c,len,a[i]);
141                      c[j]=a[i];
142                      if(j>len)//要更新len,另外补充一点:由二分查找可知j只可能比len大1
143                             len=j;//更新len
144               }
145               cout<<len<<endl;
146        }
147        return 0;
148 }*/

21:50:49

转载于:https://www.cnblogs.com/okboy/p/3224016.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值