动态规划算法

半腔热情 提交于 2020-11-15 11:46:32
  • 贪心算法:逐步建立一个解决方案,具体地优化一些局部准则。
  • 分治:将一个问题分解成独立的子问题,求解每个子问题,并将子问题的解组合起来形成原问题的解。
  • 动态规划:把一个问题分解成一系列相互重叠的子问题,并为越来越大的子问题建立解决方案。

一、weighted interval scheduling 加权区间调度

  • 问题描述:每个job有开始时间、结束时间和权重,找job不overlap的最大权重。
  • 解法1:最早结束时间优先。(若权重都一样,用贪心法是正确的,但在本题不对)。
  • 以完成时间升序标记jobs。记p(j)=i,表示j>i,在选择job j后,可选的最大下标为i。
    记OPT(j)表示由作业1,2,3…j组成的请求的最优解。
    若OPT选择j,wight包括vj,包括之前的OPT:1,2,…p(j);
    若OPT不选择j,一定包括OPT:1,2…j-1
    在这里插入图片描述



  • 解法2:暴力法
//伪代码
输入:n,s[n],f[n],v[n]
排序:根据f[n]
计算:p[n]
int computeOpt(int j){
   
   
	if(j==0)
		return 0;
	else
		return max(v[j]+computeOpt(p[j]),computeOpt(j-1));
}

分析:分层递归调用,时间复杂度指数级增长。
解决:存储每个子问题的计算结果,以备查找之需。

//伪代码.    O(nlogn) time | 
输入:n,s[n],f[n],v[n];
排序:根据f[n];//O(nlogn)
计算:p[n];
定义:m[n], m[0]=0;

int mComputeOpt(int j){
   
   //每次调用O(1) time ,总共O(n) time
	if(m[j] is empty)
		m[j]=max( v[j]+mComputeOpt(p[j]) , mComputeOpt(j-1) );
	return m[j];
}

分析:时间复杂度O(nlogn),若已由开始时间、结束时间排序O(n)。

  • 解法3:用DP算法计算最优的区间集。
findSolution(int j){
   
   
	if(j==0)
		return {
   
   };
	else if(v[j]+m[p[j]]>m[j-1])
		return {
   
   j}findSolution(p[j]);
	else 
		return findSolution(j-1);
}

分析:由于递归调用次数<=n ,因此时间复杂度为O(n)。

  • 解法4:自底向上的动态规划,展开递归。
//伪代码
bottomUp(int n,int s[],int f[],int v[]){
   
   
	sort(f[n]);
	compute p[n];
	int m[n];m[0]=0;
	for(int i=1;i<=n;i++)
		m[i]=max( v[i]+m[p[j]] , m[i-1] );
}
  • 实现:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 205;

struct Job
{
   
   
	int s, e, v, index;
	Job(int s=0,int e=0,int v=0,int index=0)//构造函数特有语法,比=效率高,有默认参数
		:s(s), e(e), v(v), index(index)
	{
   
   }
}job[maxn];

int p[maxn], m[maxn];//p记录job j前第一个不冲突的job i;m记录job 1,2..j的max value

bool cmpE(Job a, Job b)
{
   
   
	return a.e < b.e;//将job 结束时间从小到大排序
}

int main()
{
   
   
	//读入数据&初始化
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	memset(p, 0, sizeof(p));
	memset(m, 0, sizeof(m));
	int n; cin >> n;
	for (int i = 1; i <= n; i++)
	{
   
   
		cin >> job[i].s >> job[i].e >> job[i].v;
		job[i].index = i;
	}
	
	//处理:1.排序 2. 计算p 3.计算 m
	//1.
	sort(job, job + n, cmpE);
	//2.
	for (int i = n; i >0 ; i--)
	{
   
   
		for (int j = n - 1; j > 0;j--)
			if (job[i].s >= job[j].e)
			{
   
   
				p[i] = job[j].index;
				break;
			}
	}
	//3.
	for (int i = 1; i <= n; i++)
	{
   
   
		m[i] = max(job[i].v + m[p[i]], m[i - 1]);
	}

	//输出:m[n] 即max value of n jobs 
	cout << m[n] << endl;
	return 0;
}

二、segmented least squares 分段最小二乘法

  • 最小二乘:统计学基本问题——给定n个点(坐标(xi,yi)),找到一条线y = ax + b使误差的积分到达最小。
  • 问题:有n个点和一个常数c,找到一系例的线,满足min f(x)=E+cL.
    E=每个线段的误差之和;L=线的数量。
  • 动态规划解决思路:
    记OPT(j)=点p1…pj的最小误差之和。
    e(i,j)=点pi…pj的最小平方和。

在这里插入图片描述

  • 算法:
selectedLeastSquares(int n,int p[n],int c){
   
   
	for(int j=1;j<=n;j++)
		for(int i=1;i<=j;i++)
			计算e(i,j);
	int m[maxn];m[0]=0;
	for(int j=1;j<=n;j++)
		m[j]=min{
   
   eij+c+m[i-1]} ,1<=i<=j
	return m[n]
}
  • 分析:O(n3) time | O(n2) space
    每次计算e(i,j) O(n2) time ,共计算n次
    改进:通过预计算,改善为O(n2) time | O(n) space

三、knapsack problem 背包问题

  • 问题描述:给定n个物品和1个背包。每个物品i有重量wi和价值vi;背包可承重W;目标:找背包可放入物品的最大价值。
  • 贪心算法无法解决:①以价值从大到小放×②以重量从小到大放×③以vi/wi从大到小放×
  • 动态规划解法:
    定义OPT(i,w)=物品1,2…i的可放入承重w的背包的最大价值
    对于第i个物品:
    ①若i不放入背包:OPT(i,w)=OPT(i-1,w)
    ②若i放入:OPT(i,w)=OPT(i-1,w-wi)+v[i]
    在这里插入图片描述




//bottom-up
//O(n*W) time | O(n*W) space
int knapsack(int n,int W,int w[n],int v[n]){
   
   
	int OPT[maxn][maxn];
	//OPT(i,w)=0;if(i==0)
	for(int j=0;j<=W;j++)
		OPT[0][j]=0;
	//OPT(i,w)=OPT(i-1,w);if(w<wi)
	//OPT(i,w)=max( v[i]+OPT(i-1,w-wi) , OPT(i-1,w));else
	for(int i=1;i<=n;i++){
   
   
		for(int j=1;j<=W;j++){
   
   
			if(w[i]>j)
				OPT[i][j]=OPT[i-1][j];
			else
				OPT[i][j]=max( v[i]+OPT[i-1][j-w[i]] , OPT[i-1][j]);
		}
	}
	return OPT[n][W];	
}

  • 算出最优值后,可以回溯找到解。

四、RNA secondary structure RNA二级结构

  • RNA:string b;b由字母{A,C,G,U}组成。
  • 二级结构:RNA是单链的,有环(每对的末端至少间隔4个字母)、与自身形成碱基对(A-U or G-C)。
  • 问题描述:给定一个RNA序列,找到一个二级结构,使碱基对的数量最多。
  • 记OPT(j)=b1,b2…bj中最多碱基对。
    记OPT(i,j)=bi…bj中最多碱基对。
  • 选择匹配bt-bn
    查找结构(b1,b2…bt-1)=OPT(t-1)和bt+1,bt+2…bn-1。

五、sequence alignment 序列比对

P7-8

  • 记gap 为 δ,没匹配(不一样)为αpq
    cost=δ+αCGTA
  • 应用:语音识别、生物学计算、unix diff。
  • 序列对比:给定两个string,找到Min cost。
  • 定义:OPT(i,j)=Min cost of string x 1 x 2 … x i and y 1 y 2 … y j .
    ①若xi有对应yj,OPT(i,j)=( xi-yj ) +OPT(i-1,j-1)
    ②若xi无对应,OPT(i,j)=δi +OPT(i-1,j)
    ③若yj无对应,OPT(i,j)=δji +OPT(i,j-1)




  • 算法:
//伪代码
int sequenceAlignment(int m,int n,int x[m],int y[n],int δ[],int α[][]){
   
   
	//if j=0
	for(int i=0;i<m;i++)
		OPT[i,0]=δ[xi];
	//if i=0
	for(int j=0;j<n;j++)
		OPT[0,j]=δ[yj];
	//otherwise
	for(int i=1;i<m;i++){
   
   
		for(int j=1;j<n;j++){
   
   
			OPT[i][j]=min(α[xi][yi]+OPT[i-1][j-1],δ[xi]+OPT[i-1][j],δ[yj]+OPT[i][j+1]);
		}
	}
	return OPT[m][n];
}
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define maxn 105
int dp[maxn][maxn];
int M[maxn][maxn];
//题目中的 scoring matrix
void matrix(){
   
   
	M['A']['A'] = M['C']['C'] = M['G']['G'] = M['T']['T'] = 5;
	M['A']['C'] = M['C']['A'] = M['A']['T'] = M['T']['A'] = -1;
	M['-']['T'] = M['T']['-'] = -1;
	M['A']['G'] = M['G']['A'] = M['C']['T'] = M['T']['C'] = -2;
	M['T']['G'] = M['G']['T'] = M['-']['G'] = M['G']['-'] = -2;
	M['C']['G'] = M['G']['C'] = M['-']['A'] = M['A']['-'] = -3;
	M['-']['C'] = M['C']['-'] = -4;
}

int main()
{
   
   
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	matrix();
	int t; cin >> t;
	while (t--){
   
   
		//初始化&读入
		char s1[maxn], s2[maxn];
		int len1, len2;
		cin >> len1 >> s1+1; cin >> len2 >> s2+1;//将字符串输入到s1中,且从s1[1]开始输入
		//scanf("%d %s", &len2, s2 + 1);
		//处理
		dp[0][0] = 0;
		for (int i = 1; i <= len1; i++)
			dp[i][0] = dp[i - 1][0] + M[s1[i]]['-'];
		for (int j = 1; j <= len2; j++)
			dp[0][j] = dp[0][j - 1] + M['-'][s2[j]];
		for (int i = 1; i <= len1; i++){
   
   
			for (int j = 1; j <= len2; j++){
   
   
				dp[i][j] = max(dp[i - 1][j - 1] + M[s1[i]][s2[j]],
					max(dp[i - 1][j] + M[s1[i]]['-'],
					dp[i][j - 1] + M['-'][s2[j]]));
			}
		}
		//输出
		cout << dp[len1][len2] << endl;
	}
	return 0;
}
  • 总结:1.C++数组下标可以是字符(ASCII码:a-97,A-65, - 45)
    2.数组及赋值,可以放在一个函数中,main中再调用一次即可。
    3.char c[5];
    scanf("%s",c+1);//用指针的方式,传入一个字符串,直到遇到空格。
    4.max对比多个数:max(a,max(b,c));



六、Hirschberg’s algorithm 赫施伯格算法

七、Bellman-Ford algorithm 贝尔曼-福特算法

八、distance vector protocols 距离向量协议

九、negative cycles in a digraph 有向图中的负圈

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!