最长递增子序列的思索

来源:互联网 发布:复杂网络应用实例 编辑:程序博客网 时间:2024/06/16 20:42

这是一个比较古老的算法问题,在上学期的算法课中也有所讲解,不过当初并没有理解到位,现在重新思考一下。

最长递增子序列问题:给定一个序列{a0,…,ai},找出其中最长的一个序列{b0,…,bj},并且满足对任意的 0 < n,m < j,都有bn < bm。
最朴素的一种解法是从a0开始,对给定序列进行遍历,在遇到分叉时产生分支,最后将最长的序列更新出来。遍历到an时便得到最大的递增序列。这种做法较为简单,但是不可取,因为对每一个ai而言求以它为开头的递增序列可能会产生大量分支,严重影响了求解速度。
对上面的解法我们可以增加一个剪枝,如果当前要进行遍历的元素是前面某一个最长序列的一部分,那么他的序列长度肯定较小,便可以跳过此元素,直接判断下一个即可。
但是这样还是很慢,一个只有1000个数的序列,要找出最长子序列大概需要2s,这是不可容忍的,所以我搬出了动态规划。在这个问题里面,前n的数与前n-1个数的最长链有一定的联系。如果第n个数大于前面n-1个数中最长链的末端,那么更新,否则寻找链中合适的元素重新构成子序列(自身有可能是头元素)。
对这个算法,我想出了以下解法:
对每个元素为末尾进行遍历,对第i个元素,需要遍历前i-1个元素的最长子序列进行判断。然后按照算法更新相关数据,这样可以保证到第n个元素的时候一定是最长子序列。最后我按逆序输出,如果顺序输出的话单独开个数组存储一下就好。

//关于最长子序列问题的思索#include<stdio.h>int data[1000];//存储数据int answer[1000];//最长链长度int list[1000];//前导元素int max;//最长链int index;//最长链末尾索引int main(){    int n;    int i,j;    while(~scanf("%d",&n)){        for(i = 0; i < n; i++) scanf("%d",&data[i]);        answer[0] = 1;        list[0] = 0;        max = 1;        index = 0;        for(i = 1; i < n; i++){            answer[i] = 1;            list[i] = i;            for(j = i - 1; j >= 0; j--){                if(data[j] < data[i] && answer[j] + 1 > answer[i]){                    answer[i] = answer[j] + 1;                    list[i] = j;                }            }            if(answer[i] > max){                max = answer[i];                index = i;            }        }        n = list[index];        while(n != index){            printf("%d ",data[index]);            index = n;            n = list[index];        }        printf("%d\n",data[n]);    }    return 0;}

这个相比之前速度快了很多,一个1000的序列,大概100ms左右可以解决。那么还能优化吗?关键点应该在于在求前n个数的时候要对前n-1个数进行遍历,如果我们能够使用二分的思想优化,那么这个问题的时间就从一个n^2减小到nlog(n)。

#include<stdio.h>int search(int );int count;int answer[1000];int main(){    int i,n;    int number;    while(~scanf("%d",&n)){        count = 0;        scanf("%d",&answer[0]);        for(i = 1; i < n; i++){            scanf("%d",&number);            if(number < answer[0]) answer[0] = number;            else if(number > answer[count]) answer[++count] = number;            else{                answer[search(number)] = number;            }        }        printf("%d\n",count+1);    }}int search(int number){    int begin = 0,end = count;    int mid = (begin + end) / 2;    while(begin <= end){        if(answer[mid] < number)    begin = mid + 1;        else if(answer[mid] == number) return mid;        else end = mid - 1;        mid = (begin + end) / 2;    }    return mid + 1;}

这种方法占用内存较少 如果只是计数的话并不用存储之前的结果。
如果要输出最长链 只需要单独申请一个数组用来存储最长链 通过对比链中元素在最长链长度改变时进行相关的更新