zoukankan      html  css  js  c++  java
  • 动态规划初步--最长上升子序列(LIS)

    一、问题

    有一个长为n的数列 a0,a1,a2...,an-1a。请求出这个序列中最长的上升子序列的长度和对应的子序列。上升子序列指的是对任意的i < j都满足ai < aj的子序列。

    二、思路

    如果i < j且ai < aj则认为ai到aj存在有向边,由于一个数不可能直接或间接的指向自己,所以是一个有向无环图。但是,在这里我们并不需要真正的建立图。我们可以用动态规划来做,对于状态的设定有多种方式。

    三、代码实现

    1、将dp[i]表示以ai结束的最长上升子序列,当j < i且a[j] < a[i],dp[i] = max(dp[i],dp[j] + 1),初始化dp[i] = 1。由于定义的是结束位置,所以记录的是逆序,用栈倒过来再输出就行。

    代码一:

     1 #include<stdio.h>
     2 #include<iostream>
     3 #include<stack>
     4 #include<algorithm>
     5 using namespace std;
     6 
     7 const int INF = 0x3f3f3f3f;
     8 const int maxn = 1000 + 10;
     9 int n;
    10 int a[maxn], dp[maxn];    //dp[i]表示以i结束的最长上升子序列
    11 int nextp[maxn];        //记录路径
    12 int res = 0;
    13 int first = 0;
    14 
    15 void print_ans()
    16 {
    17     stack<int>s;
    18     for (int i = 0; i < res; i++)
    19     {
    20         s.push(first + 1);
    21         first = nextp[first];
    22     }
    23     for (int i = 0; i < res; i++)
    24     {
    25         int tmp = s.top(); s.pop();
    26         printf("%d ", tmp);
    27     }
    28     printf("
    %d
    ", res);
    29 }
    30 
    31 void slove()
    32 {
    33     memset(dp, 0, sizeof(dp));
    34     memset(nextp, 0, sizeof(nextp));
    35     res = 0; first = 0;                //记得清零
    36     for (int i = 0; i < n; i++)
    37     {
    38         dp[i] = 1;
    39         for (int j = 0; j < i; j++)
    40         {
    41             if (a[j] < a[i])
    42             {
    43                 if (dp[j] + 1 > dp[i])
    44                 {
    45                     dp[i] = dp[j] + 1;
    46                     nextp[i] = j;
    47                 }
    48             }
    49         }
    50         if (dp[i] > res)
    51         {
    52             res = dp[i];
    53             first = i;
    54         }
    55     }
    56     print_ans();
    57 }
    58 
    59 int main()
    60 {
    61     while (scanf("%d", &n) == 1 && n)
    62     {
    63         for (int i = 0; i < n; i++)
    64             scanf("%d", &a[i]);
    65         slove();
    66     }
    67     return 0;
    68 }

    代码二:两者只是输出的处理不同,用逆向寻路,不需要额外的空间,但要消耗一些时间。

     1 #include<stdio.h>
     2 #include<iostream>
     3 #include<stack>
     4 #include<vector>
     5 #include<algorithm>
     6 using namespace std;
     7 
     8 const int INF = 0x3f3f3f3f;
     9 const int maxn = 1000 + 10;
    10 int n;
    11 int a[maxn], dp[maxn];    //dp[i]表示以i结束的最长上升子序列
    12 stack<int>sta;
    13 
    14 void print_ans(int s)
    15 {
    16     sta.push(s + 1);
    17     if (dp[s] == 1)
    18     {
    19         while (!sta.empty())
    20         {
    21             int tmp = sta.top(); sta.pop();
    22             printf("%d ", tmp);
    23         }
    24         return;
    25     }
    26     for (int i = 0; i < n; i++)
    27     {
    28         if (a[i] < a[s] && dp[s] == dp[i] + 1)
    29         {
    30             print_ans(i);
    31             break;
    32         }
    33     }
    34 }
    35 void slove()
    36 {
    37     memset(dp, 0, sizeof(dp));
    38     int res = 0;
    39     int first = 0;
    40     stack<int>s;
    41     for (int i = 0; i < n; i++)
    42     {
    43         dp[i] = 1;
    44         for (int j = 0; j < i; j++)
    45         {
    46             if (a[j] < a[i])
    47             {
    48                 dp[i] = max(dp[i],dp[j] + 1);
    49             }
    50         }
    51         if (dp[i] > res)
    52         {
    53             res = dp[i];
    54             first = i;            //first记录最长上升子序列的最后元素的位置
    55         }
    56     }
    57 
    58     print_ans(first);
    59     printf("
    %d
    ", res);
    60 }
    61 
    62 int main()
    63 {
    64     while (scanf("%d", &n) == 1 && n)
    65     {
    66         for (int i = 0; i < n; i++)
    67             scanf("%d", &a[i]);
    68         slove();
    69     }
    70     return 0;
    71 }

    2、用dp[i]表示以i开始的最长上升子序列,初始化同样是dp[i] = 0。同时由于定义的是开始位置,可以直接输出最长上升子序列。

     1 void print_ans()
     2 {
     3     for (int i = 0; i < res; i++)
     4     {
     5         printf("%d ", first + 1);
     6         first = nextp[first];
     7     }
     8     printf("
    %d
    ", res);
     9 }
    10 
    11 void slove()
    12 {
    13     res = 0;        //定义成全局变量时,注意清零
    14     for (int i = n - 1;i >= 0; i--)
    15     {
    16         dp[i] = 1;
    17         for (int j = i + 1; j < n; j++)
    18         {
    19             if (a[i] < a[j])
    20             {
    21                 if (dp[j] + 1 > dp[i])
    22                 {
    23                     dp[i] = dp[j] + 1;
    24                     nextp[i] = j;
    25                 }
    26             }
    27         }
    28         if (dp[i] > res)
    29         {
    30             res = dp[i];
    31             first = i;
    32         }
    33     }
    34     print_ans();
    35 }

    3、把dp[i]表示长度为i + 1的上升子序列中末尾元素的最小值(不存在的话就是INF)。

    贪心 + 二分查找,利用贪心思想,对于一个一个上升子序列,显然最后一个元素越小,越有利于添加新的元素,这样序列就越长。

    类似的我们也可以定义对称的状态,即把dp[i]表示长度为i + 1的上升子序列中开始元素的最大值

    相比前面的状态定义,有两个好处:如果子序列长度相同,可以使最末尾元素的值最小;时间复杂度由O(n^2)降至O(nlogn)。

    开始全部初始化为INF,如果i == 0或者dp[i - 1] < aj,dp[i] = min(dp[i],aj),最终dp[i] < INF,最大的i + 1就是结果。如何找到aj呢,由于dp[i]记录的长度为i + 1的最末尾元素的最小值,而最末尾元素是这个长度为i+1中的最大值,所以aj大于dp[i]就能更新。我们还能发现,dp单增,每个aj只需用来更来一次。

    n个元素,每次查找logn,总的时间复杂度为O(nlogn).

     1 void slove()
     2 {
     3     fill(dp, dp + n, INF);
     4     for (int i = 0; i < n; i++)
     5     {
     6         *upper_bound(dp, dp + n, a[i]) = a[i];
     7     }
     8     int first = lower_bound(dp, dp + n, INF) - dp;
     9     printf("%d
    ", dp[first - 1]);
    10     printf("%d
    ", first);
    11 }

    打印结果路径似乎不太方便,以后再补上吧。

  • 相关阅读:
    NGUI 3.5课程(五岁以下儿童)button-图片切换
    跑openstack命令错误【You must provide a username via either -...】
    angular cors跨域资源共享设置 和formdata设定
    PHP 如何获取客户端ip地址
    JavaScript如何生成思维导图(mindmap)
    百度ueditor上传图片时如何设置默认宽高度
    英语发音规则---E字母常见的发音组合有哪些
    google搜索引擎爬虫爬网站原理
    legend2---开发日志10(ajax请求的方法是否同样会执行base控制器里面的方法)
    JS中如何判断对象是对象还是数组
  • 原文地址:https://www.cnblogs.com/lfri/p/9443120.html
Copyright © 2011-2022 走看看