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.书中解法
本质上与万金油一样是依托先前计算出来的最优划分,得出当前最优划分。
#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
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法则的最优调度 7.最优二叉搜索树 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#include
//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
void knapsack(vector