如果我是算法大师就好了

动态规划例题

2020-12-08  本文已影响0人  Aniwer

最优二分搜索树

二分搜索树

  1. 是一棵空树
  2. 具有下列性质的二叉树
    • 若左子树不空,则左子树上所有结点的值均小于它的根结点的值
    • 若右子树不空,则右子树上所有结点的值均大于它的根结点的值
    • 左、右子树分别为二叉搜索树

查询操作

MEMBER(x,S):若x在S中则返回”yes”,否则返回”no”
设有n个实数a_1<a_2<…<a_n构成集合S
指令MEMBER(x,S)中的x可能是某个a_i,也可能不在S中。把不在S中的数按区间分为n+1类,以b_0b_1,…,b_n作为每一类数的代表(虚节点)

定义

p_i为MEMBER (a_i,S)出现的频率(i=1,2,…,n)
q_j为MEMBER (b_j,S)出现的频率(j=0,1,2,…,n)
\sum_{i=1}^np_i+\sum_{j=0}^nq_j=1
定义一棵二分搜索树的总耗费:\sum_{i=1}^np_i(depth(a_i)+1)+\sum_{j=0}^nq_j(depth(b_j))
最优二分搜索树即耗费最小的二分搜索树

分析

优化

流水作业调度

问题

n个作业,m项任务,m台机器
设有n个任务,每一个作业i均被分解为m项任务:T_{i1},T_{i2},T_{im}(1≤i≤n,故共有n×m个任务),要把这些任务安排到m台机器上进行加工
需要满足条件:

  1. 每个作业i的第j项任务T_{ij}(1≤i≤n, 1≤j≤m) 只能安排在机器P_j上进行加工
  2. 作业i的第j项任务T_{ij}(1≤i≤n, 2≤j≤m)的开始加工时间均安排在第j-1项任务T_{i,j-1}加工完毕之后
  3. 任何一台机器在任何一个时刻最多只能承担一项任务
    设任务T_{ij}在机器P_j上进行加工需要的时间t_{ij},如果所有t_{ij}(1≤i≤n, 1≤j≤m)均已给出,要找出一种安排任务的方法,使得完成这n个作业的加工时间为最少,这个安排称之为最优流水作业调度
    流水作业调度一般均指的是非优先调度,即任何任务一旦开始加工,就不允许被中断,直到该任务被完成

分析

求解

进一步求解

总结

备忘录LCS

备忘录方法

求解

最长递增子序列问题(LIS)

问题

假设A =< a_1, 𝑎_2, … , 𝑎_𝑛 >为由n个不同的实数组成的序列
递增子序列𝐿 =< a_{𝑘_1}, a_{𝑘_2}, … , a_{𝑘_m}>
其中𝑘_1< 𝑘_2<…< 𝑘_𝑚并且a_{𝑘_1} < a_{𝑘_2} < … < a_{𝑘_m}
求最大的𝑚值

最大子段和问题

问题

n个整数序列a_1a_n,求该序列形如\sum_{k=i}^ja_k的子段和的最大值
当所有整数均为负整数时定义其最大子段和为0

分治解法

如果将所给的序列a[1..n]分为长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两段最大子段和,则a[1..n]的最大子段和有三种情形:

  1. a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;
  2. a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同;
  3. a[1:n]的最大子段和为\sum_{k=i}^ja_k,1≤ i ≤ n/2,n/2+1 ≤ j ≤ n
    对于1、2可以递归求解
    对于3:
    a[n/2]、a[n/2+1]在最优子序列中
    可以在a[1:n/2]中计算出s_1=max_{1≤ i ≤ n/2}\sum_{k=i}^ja_k
    可以在a[n/2+1:n]中计算出s_2=max_{n/2+1 ≤ i ≤ n}\sum_{k=n/2+1}^ia_k
    s_1+s_2即为最优

代码

void MaxSubSum(int *a,int left,int right)
{
    int sum=0;
    if (left==right) sum=a[left]>0?a[left]:0;
    else{ 
        int center=(left+right)/2;
        int leftsum=MaxSubSum(a,left,center);
        int rightsum=MaxSubSum(a,center+1,right);
        int s1=0; int lefts=0;
        for(int i=center;i>=left;i--){ 
            lefts+=a[i]; 
            if (lefts>s1) s1=lefts;
        }
        int s2=0; int rights=0;
        for (int i=center+1; i<=right; i++){
            rights+=a[i]; 
            if (rights>s2) s2=rights;
        }
        sum=s1+s2;
        if (sum<leftsum) sum=leftsum;
        if (sum<rightsum) sum=rightsum;
    }
    return sum;
}

分析

算法所需的计算时间T(n)满足典型的分治算法递归式:

T(n)=\left\{ \begin{aligned} O(1) && n≤c \\ 2T(n/2)+O(n) && n>c \\ \end{aligned} \right.

基于主方法和主定理,T(n)=O(nlogn)

动态规划解法

记b[j]=max_{1≤i<j}\sum_{k=i}^ja_k,1≤j≤n,则
max_{1≤i≤j≤n}\sum_{k=i}^ja[k]=max_{1≤j≤n}max_{1≤i≤j}\sum_{k=i}^ja[k]=max_{1≤j≤n}b[j]
因此,当b[j-1]>0,b[j]=b[j-1]+a[j];否则,b[j]=a[j]。得出
b[j]=max_{1≤j≤n}{b[j-1]+a[j],a[j]}

代码

int MaxSum(int n,int *a)
{
    int sum=0, b=0;//初始化最大子段和为0,b[0]=0
    for (int i = 1; i <= n; i++){
        if (b>0) b+=a[i];
        else b=a[i];
        if (b>sum) sum=b;//更新当前找到的最大子段和
    }
    return sum;
}

分析

O(n)

上一篇下一篇

猜你喜欢

热点阅读