打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
动态规划(2)

动态规划(Dynamic Programming)是算法的一种类型,它通过将某个复杂问题分解为子问题,通过解决子问题并保存子问题的结果,避免重新计算同样的子问题,进而解决原来的复杂问题。

如果某个问题具有下面两个属性,那么它可能适合于用动态规划算法来解决:

1)重叠的子问题(Overlapping Subproblems)

2)最优的子结构(Optimal Substructure)

在上节课中,我们介绍了动态规划的第一个属性——重叠的子问题。

本课介绍它的第二个属性——最优的子结构。


最优的子结构

我们来看看什么是最优的子结构。

最优的子结构:对于一个给定的问题,如果通过利用其子问题的最优解,可以获得给定问题的最优解,那么我们说这个给定问题具有最优子结构属性。

例如,最短路径问题具有以下最优子结构属性:

如果节点x位于从源节点u到目的节点v的最短路径中,则从u到v的最短路径是从u到x的最短路径和从x到v的最短路径的组合。

因此求两个节点之间的最短路径的一些标准算法,例如:Floyd-Warshall算法和Bellman-Ford算法,都是采用动态编程来解决问题的典型例子。

另一方面,最长路径问题没有最佳子结构属性。这里的最长路径是指两个节点之间最长的简单路径(没有循环的路径)。

我们看下面的例子。

从q到t有两条最长的路径:q→r→tq→s→t。与最短路径不同,这些最长路径不具有最佳子结构属性。

例如,最长路径q→r→t不是从q到r的最长路径和从r到t的最长路径的组合:

因为从q到r的最长路径是q→s→t→r;从r到t的最长路径是r→q→s→t

q→r→t  不等于   q→s→t→r r→q→s→t

所以找q到t的最长路径不能拆分为找q到r的最长路径和r到t的最长路径的组合的方式,因此它不具备最优的子结构的特性。

最长递增序列问题

以最长递增子序列(LIS)问题为例,我们一起看看如何使用动态规划来解决它。

最长递增子序列(LIS)问题是用于找出给定序列的中一个最长子序列,使得子序列的所有元素按递增顺序排序。 例如,对于序列{10,22,9,33,21,50,41,60,80},它的子序列{10,22,33,50,60,80}中的元素依次递增排列,具有最长的长度,所以它的LIS长度为6

更多的例子:

输入: arr[] = {3, 10, 2, 1, 20}

输出: Length of LIS = 3

最长的递增序列为3, 10, 20


输入: arr[] = {3, 2}

输出: Length of LIS = 1

最长的递增序列为{3} and {2}

输入:arr[] = {50, 3, 10, 7, 40, 80}
输出:Length of LIS = 4
最长的递增序列 {3, 7, 40, 80}

最长递增序列问题的最佳子结构

假设arr [0..n-1]为输入数组,L(i)为在索引i处结束的LIS的长度,arr[i]是LIS的最后一个元素。

这样,L(i) 可以递归写为:

1)    L(i) = 1 + max(L(j))其中0<j<i且arr[j]<arr[i];

2)    L(i) = 1,如果不存在这样的j。


要找到给定数组的LIS,我们需要返回max(L(i)),其中0<i<n。因此,我们看到LIS问题满足最优子结构属性,可以使用子问题的解来找出原来问题的解。

下面是上面递归算法的简单实现。

#include<stdio.h>
#include<stdlib.h>
  
int _lis( int arr[], int n, int *max_ref)
{
    if (n == 1)
        return 1;
    int res, max_ending_here = 1; 
 
    for (int i = 1; i < n; i++)
    {
        res = _lis(arr, i, max_ref);
        if (arr[i-1] < arr[n-1] && res + 1 > max_ending_here)
            max_ending_here = res + 1;
    }
   
    if (*max_ref < max_ending_here)
       *max_ref = max_ending_here;
 
    return max_ending_here;
}
int lis(int arr[], int n)
{
    int max = 1;

    _lis( arr, n, &max );

    return max;
}
int main()
{
    int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
    int n = sizeof(arr)/sizeof(arr[0]);
    printf('Length of lis is %dn',
           lis( arr, n ));
    return 0;
}

改成动态规划编程

上述程序实现中,很明显具有重叠的子问题特性。

例如计算lis(4)的过程如下图所示。


很明显,许多相同的lis(k)被重复计算,具备重叠的子问题特性。可以对算法进行优化。

这里采用列表方式的优化方式。改进后的程序的实现如下:

#include<bits/stdc++.h>  
using namespace std;

int lis( int arr[], int n )  
{  
    int lis[n];
    lis[0] = 1;    
   
    for (int i = 1; i < n; i++ )  
    {
        lis[i] = 1;
        for (int j = 0; j < i; j++ )   
            if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)  
                lis[i] = lis[j] + 1;  
    }
    return *max_element(lis, lis+n);
}  
    
int main()  
{  
    int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };  
    int n = sizeof(arr)/sizeof(arr[0]);  
    printf('Length of lis is %d\n', lis( arr, n ) );  
    return 0;  
}

课后请运行本课中的程序,并思考如何解决下面的最长链问题:


假设有一个包含很多个数字对的集合。在每一个数字对中,第一个数字小于第二个数字。比如,{(1,3),(2,4),(6,7),(3,9),(4,5)}。并且对于其中的任何两组数字对(a,b)和(c,d),如果b <c,则(c,d)可以接在(a,b)之后,形成(a,b)—>(c,d)的链条。用类似的方式,可以形成一个长链。请你找到可以形成的最长链。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Python|取珠宝问题
动态规划算法入门,需要搞懂这5个问题
白话算法之【动态规划入门】
算法面试题:草坪修整
最长上升子序列(LIS)长度的O(nlogn)算法
最长递增子序列 O(NlogN)算法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服