动态规划(算法分析与设计)

0.简单动态规划做法相关笔记

A.动态规划算法设计

1.找出最优解性质,刻画最优解结构(有什么窍门?周五上课问老师)

我们把这种子问题最优时,母问题通过优化选择后一定最优的情况叫做“最优子结构”。

2.递归的定义最优值(子问题重叠)

自顶而下定义最优值(备忘录法)

3.以自底向上的方式计算最优值

自底而上的迭代

4.通过子问题的最优值构造原问题的最优解

母问题通过对子问题最优值的优化选择,得出最优解

B.动态规划中一些代码的模板(矩阵连乘,图像压缩,0-1背包差不多就这三种模板套)

1.起点,终点都变动m[ i ][ j ](O(n^3))

int Count(int n)
{
	for(int i=0;i<=n;i++)
	{
		m[i][i]=v[i];
	} 
	for(int r=2;r<=n+1;r++)//本次划分成的小组,每小组元素的个数 
	{
		for(int i=0;i<=n-r+1;i++)//m[i][j],左下标i的上界为:(最右下标-小组元素个数+1) 
		{
			int j=i+r-1;//m[i][j]右下标的标号为:(左下标序i+小组元素个数-1)
			m[i][j]=GetV(m[i][i],m[i+1][j],i,i+1);
			for(int k=i+1;km[i][j])
				{
					m[i][j]=temp;
				}	
			}
		}
	}
	return m[0][n];
}

2.起点终点只有一个变动(O(n^2))

int CountMin(vector

&p,vector &s,vector &l) { int N=p.size()-1;//元素最大下标 for(int i=1;i<=N;i++)//从1开始遍历,p[0]无效,s[0]无效 { int bmax=p[i].bitp; s[i]=s[i-1]+bmax;//s[i]初始化为s[i-1]已经得出的i-1的最佳划分与最后一个元素自成一段 l[i]=1;//从下标i起,该段长度 for(int j=2;j<=i&&j<=lmax;j++)//j为该段元素可能的个数,从2个到256限制,或到目标位置i为止 { if(bmaxs[i-j]+j*bmax)//该段向左扩张,依次比较扩张一个单元,与不扩张该单元的大小 { s[i]=s[i-j]+j*bmax; l[i]=j; } } s[i]+=header; } return s[N]; }

C.动态规划与分治的一点区别

1.动态规划适用问题类型:(组合最优化和最优化问题)优化问题。分治适用问题类型:通用问题

3.动态规划的实现方法一般是自底向上,分治是自顶向下

4.分治法将子问题看成独立的(如果实际上子问题不独立,那就要重复求解公共子问题),动态规划将子问题看成联系的(各子问题包含公共子子问题,对每个子子问题只求解一次)。

5.动态规划的子问题规模通常只比原问题规模小1,分治的子问题之间的规模通常差不多,比原问题规模要小上一大截

1.矩阵连乘问题

参考博客:http://blog.sina.com.cn/s/blog_64018c250100s123.html

备忘录方法求解与动态规划求解之间的关系,有些像《分治递归》中非尾递归转非递归的关系。

A.动态规划---自底而上的迭代

a.i*k型号的矩阵与k*j型号的矩阵相乘,乘法次数为i*k*j,即行i*列k*列j,即列(i-1)*列k*列j

b.假如任意一个规模小于j-i+1的矩阵最少乘法次数m[ i ][ k ]已知,那么j-i+1规模的矩阵连乘最少次数也应易知m[ i ][ j ]=min{ m[ i ][ k ]+m[ k ][ j ]+i*k*j },故所求者只是划分位置k

#include
#include
#include
using namespace std;
void matrixChain(vector &p,vector< vector > &m,vector< vector > &s)
//p[]保存的是矩阵i~j的列数;m[a][b]用于保存b-a+1规模的矩阵连乘的最少乘法次数;s[a][b]保存b-a+1规模的矩阵连乘的最佳分割点k 
{
	int n=p.size()-1;//矩阵连乘最大序号
	for(int i=1;i<=n;i++)
	{
		m[i][i]=0;//矩阵连乘的矩阵下标是从i=1开始的,单个矩阵时,乘法次数为0 
	}	
	for(int r=2;r<=n;r++)//r为每组元素个数,每组元素个数的递增使得循环由计算m[i][i+1](i=1~n)到计算m[i][j](n>j>i+1) 
	{
		for(int i=1;i<=n-r+1;i++)//从m[2][3],m[3][4],m[5][6]算起,随着每组元素个数的增多开始算m[1][10]这样的数,
								//这样可以保证沿途记录了较大m[i][j]计算所需的m[i][k]和m[k][j]的确切值(i > &s,int i,int j)
{
	if(i==j)
	{
		return;
	}
	traceback(s,i,s[i][j]);
	traceback(s,s[i][j]+1,j);
	cout<<"A"< p;
	vector< vector > m;
	vector< vector > s;
	cout<<"please input the number of matrix!"<>n;
	for(int i=0;i<=n;i++)
	{
		p.push_back(randrom(1,20));
		vector temp;
		for(int j=0;j<=n;j++)
		{
			temp.push_back(-1);
		}
		m.push_back(temp);
		s.push_back(temp);
	}
	matrixChain(p,m,s);
	cout<<"please input i and j!"<>i>>j;
	traceback(s,i,j);
} 

B.备忘录---自顶而下的递归

#include
#include
#include
using namespace std;
int matrixChain(vector &p,vector< vector > &m,vector< vector > &s,int i,int j)
{
	if(m[i][j]>0)//若已经记录,就直接返回记录值 
	{
		return m[i][j];//m为备忘录,记载已经得到确切值的m[i][j] 
	}
	else if(i==j)//单个矩阵,乘法次数为0,直接返回0 
	{
		return 0;
	}
	//其余,递归求解 
	else
	{
		m[i][j]=m[i][i]+matrixChain(p,m,s,i+1,j)+p[i-1]*p[i]*p[j];//初始化为Ai*(Ai+1~Aj)
		s[i][j]=i; 
		for(int k=i+1;k > &s,int i,int j)
{
	if(i==j)
	{
		return;
	}
	traceback(s,i,s[i][j]);
	traceback(s,s[i][j]+1,j);
	cout<<"A"< p;
	vector< vector > m;
	vector< vector > s;
	cout<<"please input the number of matrix!"<>n;
	for(int i=0;i<=n;i++)
	{
		p.push_back(randrom(1,20));
		vector temp;
		for(int j=0;j<=n;j++)
		{
			temp.push_back(0);
		}
		m.push_back(temp);
		s.push_back(temp);
	}
	matrixChain(p,m,s,1,n);
	cout<<"please input i and j!"<>i>>j;
	traceback(s,i,j);
} 

2.最长公共子序列

A.备忘录

#include 
#include
#include
#include
using namespace std;
int random(int start,int end)
{
	return start+rand()%(end-start+1);
}
//和矩阵连乘的思想一样,如果我们能知道链长为ix和链长为jy的最长公共子序列的确切值m[ix][jy](ix &x,vector &y,vector< vector > &m,int i,int j)
{
	if(m[i][j]>0)
	{
		return m[i][j];
	}
	if(i==0||j==0)
	{
		return 0;
	}
	if(x[i]==y[j])
	{
		m[i][j]=lcslength(x,y,m,i-1,j-1)+1;//最后一个元素相等,m[i][j]=m[i-1][j-1]+1 
		return m[i][j];
	}
	m[i][j]=max(lcslength(x,y,m,i,j-1),lcslength(x,y,m,i-1,j));//最后一个元素不相等,那就是max{m[i][j-1],m[i-1][j]},即两个链表中的一个链表要去掉一个元素 
	return m[i][j];
}
int main()
{
	vector x;
	vector y;
	int xlength;
	int ylength;
	cin>>xlength>>ylength;	
	cout<<"x is:"< > m;
	for(int i=0;i temp;
		for(int j=0;j

B.动态规划

#include 
#include
#include
#include
#include
using namespace std;
int random(int start,int end)
{
	return start+rand()%(end-start+1);
}
void lcslength(vector &x,vector &y,vector< vector > &m,int xi,int yj,vector< vector >&b)
{
	if(xi==0||yj==0)
	{
		return;
	}
	for(int i=1;i<=xi;i++)
	{
		for(int j=1;j<=yj;j++)
		{
			if(x[i]==y[j])
			{
				m[i][j]=m[i-1][j-1]+1;
				b[i][j]=1;
			}
			else
			{
				m[i][j]=max(m[i][j-1],m[i-1][j]);
				if(m[i][j-1]>m[i-1][j])
				{
					b[i][j]=2;
				}
				else
				{
					b[i][j]=3;
				}
			}
		}
	}
}
stack s;
void showlcs(vector< vector > &b,int i,int j,vector &x,vector &y)
{
	if(i==0||j==0)
	{
		while(!s.empty())
		{
			cout< x;
	vector y;
	int xlength;
	int ylength;
	cout<<"please input xlength and y length!"<>xlength>>ylength;	
	cout<<"x is:"< > m;
	vector< vector > b;
	for(int i=0;i temp;
		for(int j=0;j

3.多边形游戏(O(n^4))

书上做法的时间复杂度是O(n^3)。我的做法是遍历的删除其中一条边(该问题就成了矩阵连乘问题了),算出最大值,各最大值再比较。

A.备忘录

#include
#include
#include
using namespace std;

vector< vector > eage;//边运算 
vector v;//顶点值 
vector< vector > m;//m[i][j] 
int vertexnum;//顶点个数

void Move();//向左移动一个元素(同时移动元素之间的运算符位置) 
void InitME(vector< vector > &x,int num);//各种初始化 
void Show();//显示计算式子 
int Random(int start,int end);//随机数生成 
int GetV(int a,int b,int i,int j);//根据运算符,产生计算结果 
int Count(int left,int right);//计算给定元素的最大值 

int main()
{ 
	cin>>vertexnum;
	InitME(eage,1);
	InitME(m,1);
	InitME(eage,3);
	int maxv=-1;
	for(int i=0;i > &x,int num)
{
	switch(num)
	{
		case 1:
			for(int i=0;i temp;
				for(int j=0;j0)
	{
		return m[left][right];
	}
	if(left==right)
	{
		return v[left];
	}
	m[left][right]=GetV(Count(left,left),Count(left+1,right),left,left+1);
	for(int k=left+1;km[left][right])
		{
			m[left][right]=temp;
		}
	}
	return m[left][right];
}

B.动态规划,我的做法,用万金油暴力解(O(n^4))(用矩阵连乘的方法解)

#include
#include
#include
using namespace std;

vector< vector > eage;//边运算 
vector v;//顶点值 
vector< vector > m;//m[i][j] 
int vertexnum;//顶点个数

void Move();//向左移动一个元素(同时移动元素之间的运算符位置) 
void InitME(vector< vector > &x,int num);//各种所需的初始化 
void Show();//显示计算式子 
int Random(int start,int end);//随机数生成 
int GetV(int a,int b,int i,int j);//根据运算符,产生计算结果 
int Count(int n);//计算给定元素的最大值 

int main()
{ 
	cin>>vertexnum;
	InitME(eage,1);
	InitME(m,1);
	InitME(eage,3);
	int maxv=-99999;
	for(int i=0;i > &x,int num)
{
	switch(num)
	{
		case 1:
			for(int i=0;i temp;
				for(int j=0;jm[i][j])
				{
					m[i][j]=temp;
				}	
			}
		}
	}
	return m[0][n];
}

4.图像压缩

A.我的做法,用万金油暴力求解(O(n^3))(用矩阵连乘的解法)

忘记考虑每段不大于256个元素的条件

/********************************************************************************************************************** 
本质上还是使用矩阵连乘的方法,用m[i][j]记录从i到j子问题的最优解,从计算m[0][1],m[1][2]等的两元组,到m[0][0+r]的r元组,
最后依托已计算出的m[0][0+r]计算出m[0][max]。算法复杂度是n^3  
凡是可以展成链式的,都可以用矩阵连乘的方法来做,如矩阵连乘,最长公共子序列,凸多边形最优三角剖分,多边形游戏,图像压缩,
不是环,算法复杂度都在n^3,算是动态规划中的暴力解 
另:王晓东书上做法的算法复杂度是n 
***********************************************************************************************************************/ 
#include
#include
#include
#include
using namespace std;

typedef struct P
{
	int p;
	int bitp;
};
vector

p; int pnum; vector< vector > m; int Random(int start,int end) { return start+rand()%(end-start); } int CountCost(int i,int j) { int tempb=-1; for(int k=i;k<=j;k++) { if(tempb>pnum; for(int i=0;i t; for(int j=0;j

B.书中解法

本质上与万金油一样是依托先前计算出来的最优划分,得出当前最优划分。

动态规划(算法分析与设计)_第1张图片

#include
#include
#include
#include
using namespace std;

typedef struct P
{
	int p;
	int bitp;
};
const int lmax=256;//每段不大于256个元素
const int header=11;//每段固定用3位记录段s[i]的b[i]长度,用8位记录段s[i]的长度l[i] 

int Random(int start,int end)
{
	return start+rand()%(end-start);
}
void InitX(vector &x,int n)
{
	for(int i=0;i &p,vector &s,vector &l)
{
	int N=p.size()-1;//元素最大下标 
	for(int i=1;i<=N;i++)//从1开始遍历,p[0]无效,s[0]无效 
	{
		int bmax=p[i].bitp;
		s[i]=s[i-1]+bmax;//s[i]初始化为s[i-1]已经得出的i-1的最佳划分与最后一个元素自成一段 
		l[i]=1;//从下标i起,该段长度 
		for(int j=2;j<=i&&j<=lmax;j++)//j为该段元素可能的个数,从2个到256限制,或到目标位置i为止 
		{
			if(bmaxs[i-j]+j*bmax)//该段向左扩张,依次比较扩张一个单元,与不扩张该单元的大小 
			{
				s[i]=s[i-j]+j*bmax;
				l[i]=j;
			}
		}
		s[i]+=header;
	}
	return s[N];
}
int main()
{
	vector

p;//存放像素值,及其存储该像素点所需位数 vector s;//存放0~i的,最小存储所用内存大小 vector l;//存放每段所包含的元素个数 int pnum;//像素点个数 cin>>pnum; for(int i=0;i

5.电路布线

(1)当i=1时


(2)当i>1时


#include
#include
#include 
#include
using namespace std;
int mnset(vector< vector > &m,map &c)
{
	int N=c.size()-1;
	for(int j=0;j > m;
	map c;
	int num;
	cout<<"please input the number of wiring terminal"<>num;
	cout<<"please input c[i]"< temp;
		int t;
		cin>>t;
		for(int j=0;j

6.流水作业调度

A.Johnson法则

解读:

1.M1的工作时间为ai,M2的工作时间为bi

2.bj大于ai时,若ai

3.ai大于bj时,若bi>bj,bi>ai,( i < j )满足johnson法则。即若某些作业的bi>ai,则该类作业需按bi的降序排列,才能满足johnson法则

4.ai>bi的工作集,应该排在ai

B.流水作业调度问题的johnson算法

(1)令N1={ i | ai < bi },N2={ i | bi <= ai }

(2)N1中作业按ai升序排序,N2中作业按bi降序排序

(3)N1中作业接N2中作业构成满足johnson法则的最优调度

#include
#include
#include
using namespace std;
class Node
{
	private:
		int key;
		int index;
		bool job;
	public:
		Node(int kk,int ii,int jj):key(kk),index(ii),job(jj){}
		int getKey()
		{
			return key;
		}
		int getIndex()
		{
			return index;
		}
		bool getJob()
		{
			return job;
		}
};
int random(int start,int end)
{
	return start+rand()%(end-start);
}
bool cmp(Node a,Node b)
{
	return a.getKey() &a,vector &b,vector &c)
{
	int n=a.size();
	vector d;//排序分类辅助数组
	//d的key中有ai和bi,bi的d的job标记为true,为N1,ai的d的job标记为false,为N2.按关键字升序排序,再通过遍历筛选,前半段为N1,升序排列,后半段为N2,降序排列
	for(int i=0;ib[i]?b[i]:a[i];//关键字,保证关键字b[i](或a[i])大于未被列为关键字的a[i](或b[i])
		bool job=(a[i]<=b[i]);
		d.push_back(Node(key,i,job));
	}
	sort(d.begin(),d.end(),cmp);//按关键字升序排列
	int j=0;
	int k=n-1;
	for(int i=0;i a;
	vector b;
	vector c;
	int num;
	cin>>num;
	for(int i=0;i

7.最优二叉搜索树

//a[]存储访问不命中概率x=(xi,xi+1),b[]存储各内节点命中概率x=xi。两者之和为1。a[j]表示查到节点j,但是x=(xj,xj+1)未命中;b[ j ]表示查到节点j,且x=xj命中
//m[][]为子树期望代价
//w[][]为子树概率总和(即访问到本子树根节点的概率) ,w[i][j]=a[i-1]+b[i]+a[i]+...+b[j]+a[j] (1<=i<=j<=n)
void OPBST(int a[],int b[],vector< vector > &m,vector< vector > &w)
{
	int n=a.size()-1;//节点个数
	for(int i=0;i<=n;i++)
	{
		w[i+1][i]=a[i];//w[i][j]=a[i-1]+....,初始化 
		m[i+1][i]=0;
	}
	for(int r=0;r

8.0-1背包问题

在考虑最优子结构时,真的没想到背包大小也能变(是j,不是C)

即先需要解出背包可选范围[ i+1 , n ]的情况下,背包容积从0~C时,各个层次m[i+1][0],m[i+1][1],m[i+1][2],m[i+1][3],。。。m[i+1][C-1],m[i+1][C]的最优解,以供可选范围增大至[ i , n ]时,背包容积从0~C,各个层次m[i][0],m[i][1],m[i][2],m[i][3],。。。m[i][C-1],m[i][C]是否需要把物品放入背包。(m[ i ] [ j ] = m[ i+1 ] [ j ] 不需要放入)(m[  i  ] [ j ] = m [ i+1 ] [ j-w[ i ] ] + v[ i ] 需要放入)

图不错,讲解也很好:http://blog.csdn.net/mu399/article/details/7722810

void knapsack(vector &v,vector &w,int c,vector< vector > &m)
{//v[i]是物品i的价值,w[i]是物品i的重量,c是给定的背包大小,m[i][j]是指在可选物品[i,n]的范围内,背包容积在j(0<=j<=C)大小的情况下最大能装下的价值
	int n=v.size()-1;
	int JMax=min(w[n]-1,c);
	for(int j=0;j<=JMax;j++)//在只能选择物品n,且背包大小装不下w[n]时,背包能装下的最大价值为0
	{
		m[n][j]=0; 
	}
	for(int j=w[n];j<=c;j++)//在只能选择物品n,且背包大小装得下w[n]时,背包能装下的最大价值为v[n]
	{
		m[n][j]=v[n];
	}
	for(int i=n-1;i>1;i--)
	{
		JMax=min(w[i]-1,c);
		for(int j=0;j<=JMax;j++)//在选择物品范围[i,n],且背包大小装不下w[i]时,背包能装下的最大价值就是选择范围为[i+1,n]时,能装下的最大价值
		{
			m[i][j]=m[i+1][j];
		}	
		for(int j=w[i];j<=c;j++)//能装下物品w[i]时,就要衡量是否要装物品i了
		{
			m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
		}
	}
	m[1][c]=m[2][c];
	if(c>=w[1])
	{
		m[1][c]=max(m[2][c],m[2][c-w[1]]+v[1]);
	}
}




你可能感兴趣的:(数据结构与基础算法)