您的位置:首页 > 其它

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

2015-10-04 20:11 681 查看
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;k<j;k++)//组内划分,从最下标+1开始,到右下标结束
{
int temp=GetV(m[i][k],m[k+1][j],k,k+1);
if(temp>m[i][j])
{
m[i][j]=temp;
}
}
}
}
return m[0]
;
}

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

int CountMin(vector<P> &p,vector<int> &s,vector<int> &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(bmax<p[i-j+1].bitp)//遍历是从靠近i的最左边元素i-1开始的
{
bmax=p[i-j+1].bitp;
}
if(s[i]>s[i-j]+j*bmax)//该段向左扩张,依次比较扩张一个单元,与不扩张该单元的大小
{
s[i]=s[i-j]+j*bmax;
l[i]=j;
}
}
s[i]+=header;
}
return s
;
}


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<iostream>
#include<vector>
#include<cmath>
using namespace std;
void matrixChain(vector<int> &p,vector< vector<int> > &m,vector< vector<int> > &s)
//p[]保存的是矩阵i~j的列数;m[a]用于保存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<k<j),就实现了自底向上的计算过程。
{
int j=i+r-1;//m[i][j]即为m[i][i+(r-1)],i循环,r-1递增
m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];//m[i][j]初始化为(Ai * (Ai+1~Aj最小乘法次数) ),因m[i][i]=0,所以其实可以省略掉m[i][i]
s[i][j]=i;//当前j+i-1规模矩阵连乘最佳分割处为k=i
for(int k=i+1;k<j;k++)//遍历(i+1)~(j-1)寻找最小乘法次数及最佳分割点,上界为j是因为m[][]的确切值,只算到了m[][j];下界自然是i+1了
{					  //由于j=i+r-1,且i是递增的,所以整一个m[][]的记录是自底向上的,
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];//i*j型矩阵与j*k型矩阵相乘乘法次数为i*j*k,除p[0]存储的是第1个矩阵的行数,p[i]存储的是第i个矩阵的列数
if(t<m[i][j])							//矩阵i的行等于矩阵i-1的列,所以行i*列k*列j,等于列i-1*列k*列j,即p[i-1]*p[k]*p[j]
{
m[i][j]=t;
s[i][j]=k;
}
}
}
}
}
int randrom(int start,int end)
{
return start+rand()%(end-start+1);
}
void traceback(vector< vector<int> > &s,int i,int j)
{
if(i==j)
{
return;
}
traceback(s,i,s[i][j]);
traceback(s,s[i][j]+1,j);
cout<<"A"<<i<<","<<s[i][j]<<"and A"<<s[i][j]+1<<","<<j<<endl;
}
int main()
{
vector<int> p;
vector< vector<int> > m;
vector< vector<int> > s;
cout<<"please input the number of matrix!"<<endl;
int n;
cin>>n;
for(int i=0;i<=n;i++)
{
p.push_back(randrom(1,20));
vector<int> 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!"<<endl;
int i,j;
cin>>i>>j;
traceback(s,i,j);
}

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


#include<iostream>
#include<vector>
#include<cmath>
using namespace std;
int matrixChain(vector<int> &p,vector< vector<int> > &m,vector< vector<int> > &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<j;k++)//求m[i][j]的确切值
{
int t=matrixChain(p,m,s,i,k)+matrixChain(p,m,s,k+1,j)+p[i-1]*p[k]*p[j];
if(t<m[i][j])
{
m[i][j]=t;
s[i][j]=k;
}
}
return m[i][j];
}
}
int randrom(int start,int end)
{
return start+rand()%(end-start+1);
}
void traceback(vector< vector<int> > &s,int i,int j)
{
if(i==j)
{
return;
}
traceback(s,i,s[i][j]);
traceback(s,s[i][j]+1,j);
cout<<"A"<<i<<","<<s[i][j]<<"and A"<<s[i][j]+1<<","<<j<<endl;
}
int main()
{
vector<int> p;
vector< vector<int> > m;
vector< vector<int> > s;
cout<<"please input the number of matrix!"<<endl;
int n;
cin>>n;
for(int i=0;i<=n;i++)
{
p.push_back(randrom(1,20));
vector<int> 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!"<<endl;
int i,j;
cin>>i>>j;
traceback(s,i,j);
}

2.最长公共子序列

A.备忘录

#include<iostream>
#include<vector>
#include<cmath>
#include<cstdlib>
using namespace std;
int random(int start,int end)
{
return start+rand()%(end-start+1);
}
//和矩阵连乘的思想一样,如果我们能知道链长为ix和链长为jy的最长公共子序列的确切值m[ix][jy](ix<i;iy<y),
//那么加上一个元素,我们就能算出 m[ix+1][jy]的最长公共子序列的确切值,依次增加ix,jy,可推出我们最终可以确切知道m[i][j]
//这个子集从矩阵连乘和最长公共子序列来看,并不是像分治那般,切割成差不多规模的子集。而是这个子集+1就可以成为父集,是属于一个元素一个元素剥下来的
//m[i][j]记录长i的链x,与长j的链y的公共子序列,和矩阵连乘一样有一个二维数组来记录原问题(这里记录最长公共子序列的长度,矩阵连乘记录最少乘法次数)
//且都是从原子问题逐步扩大
int lcslength(vector<int> &x,vector<int> &y,vector< vector<int> > &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<int> x;
vector<int> y;
int xlength;
int ylength;
cin>>xlength>>ylength;
cout<<"x is:"<<endl;
x.push_back(-1);//从下标1开始存储元素,递归中当i=0或j=0时返回,若在下标0处开始存储,首位就检测不到了
for(int i=1;i<xlength;i++)
{
x.push_back(random(1,3));
cout<<x[i]<<"\t";
}
cout<<"\ny is:"<<endl;
y.push_back(-1);//从下标1开始存储元素
for(int i=1;i<ylength;i++)
{
y.push_back(random(1,3));
cout<<y[i]<<"\t";
}
cout<<endl;
vector< vector<int> > m;
for(int i=0;i<xlength;i++)
{
vector<int> temp;
for(int j=0;j<ylength;j++)
{
temp.push_back(0);
}
m.push_back(temp);
}
cout<<lcslength(x,y,m,xlength-1,ylength-1)<<endl;;
}

B.动态规划

#include<iostream>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
using namespace std;
int random(int start,int end)
{
return start+rand()%(end-start+1);
}
void lcslength(vector<int> &x,vector<int> &y,vector< vector<int> > &m,int xi,int yj,vector< vector<int> >&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<int> s;
void showlcs(vector< vector<int> > &b,int i,int j,vector<int> &x,vector<int> &y)
{
if(i==0||j==0)
{
while(!s.empty())
{
cout<<s.top()<<" ";
s.pop();
}
return;
}
if(b[i][j]==1)
{
s.push(x[i]);
showlcs(b,i-1,j-1,x,y);
}
else if(b[i][j]==2)
{
showlcs(b,i,j-1,x,y);
}
else
{
showlcs(b,i-1,j,x,y);
}
}
int main()
{
vector<int> x;
vector<int> y;
int xlength;
int ylength;
cout<<"please input xlength and y length!"<<endl;
cin>>xlength>>ylength;
cout<<"x is:"<<endl;
x.push_back(-1);//从下标1开始存储元素,递归中当i=0或j=0时返回,若在下标0处开始存储,首位就检测不到了
for(int i=1;i<xlength;i++)
{
x.push_back(random(1,3));
cout<<x[i]<<"\t";
}
cout<<"\ny is:"<<endl;
y.push_back(-1);//从下标1开始存储元素
for(int i=1;i<ylength;i++)
{
y.push_back(random(1,3));
cout<<y[i]<<"\t";
}
cout<<endl;
vector< vector<int> > m;
vector< vector<int> > b;
for(int i=0;i<xlength;i++)
{
vector<int> temp;
for(int j=0;j<ylength;j++)
{
temp.push_back(0);
}
m.push_back(temp);
b.push_back(temp);
}
lcslength(x,y,m,xlength-1,ylength-1,b);
cout<<"\nm["<<xlength-1<<"]["<<ylength-1<<"]: "<<m[xlength-1][ylength-1]<<endl;
showlcs(b,xlength-1,ylength-1,x,y);
}

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

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

A.备忘录

#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;

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

void Move();//向左移动一个元素(同时移动元素之间的运算符位置)
void InitME(vector< vector<int> > &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<vertexnum;i++)
{
int temp=Count(0,vertexnum-1);
if(maxv<temp)
{
maxv=temp;
}
Show();
cout<<" = "<<temp<<endl;
Move();
InitME(m,2);
}
cout<<"max = "<<maxv<<endl;
}

void Show()
{
for(int i=0;i<vertexnum-1;i++)
{
cout<<v[i];
if(eage[i][i+1]==1)
{
cout<<" + ";
}
else if(eage[i][i+1]==2)
{
cout<<" * ";
}
}
cout<<v[vertexnum-1]<<"\t"<<eage[0][vertexnum-1];
}

void InitME(vector< vector<int> > &x,int num)
{
switch(num)
{
case 1:
for(int i=0;i<vertexnum;i++)
{
vector<int> temp;
for(int j=0;j<vertexnum;j++)
{
temp.push_back(0);
}
x.push_back(temp);//m初始化
}
return;
case 2:
for(int i=0;i<vertexnum;i++)
{
for(int j=0;j<vertexnum;j++)
{
x[i][j]=0;
}
}
return;
case 3:
for(int i=0;i<vertexnum;i++)
{
int temp;
temp=Random(1,10);
v.push_back(temp);//顶点随机赋值
for(int j=i+1;j<vertexnum;j++)
{
temp=Random(1,2);//边赋运算随机赋值,1表示+,2表示乘,0表示无效
eage[i][j]=temp;
eage[j][i]=temp;
}
}
return;
};
}

void Move()
{
int temp1=v[0];
int temp2=eage[0][1];
for(int j=1;j<vertexnum-1;j++)
{
v[j-1]=v[j];
eage[j-1][j]=eage[j][j+1];
eage[j][j-1]=eage[j][j+1];
}
v[vertexnum-2]=v[vertexnum-1];
eage[vertexnum-2][vertexnum-1]=eage[vertexnum-1][0];
eage[vertexnum-1][vertexnum-2]=eage[vertexnum-1][0];

v[vertexnum-1]=temp1;
eage[vertexnum-1][0]=temp2;
eage[0][vertexnum-1]=temp2;
}

int Random(int start,int end)
{
return start+rand()%(end-start+1);
}

int GetV(int a,int b,int i,int j)
{
switch(eage[i][j])
{
case 0:
return 0;
case 1:
return a+b;
case 2:
return a*b;
}
}

int Count(int left,int right)
{
if(m[left][right]>0)
{
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;k<right;k++)
{
int temp=GetV(Count(left,k),Count(k+1,right),k,k+1);
if(temp>m[left][right])
{
m[left][right]=temp;
}
}
return m[left][right];
}


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

#include<iostream>
#include<vector>
#include<cstdlib>
using namespace std;

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

void Move();//向左移动一个元素(同时移动元素之间的运算符位置)
void InitME(vector< vector<int> > &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<vertexnum;i++)
{
int temp=Count(vertexnum-1);
if(maxv<temp)
{
maxv=temp;
}
Show();
cout<<" = "<<temp<<endl;
Move();
InitME(m,2);
}
cout<<"max = "<<maxv<<endl;
}

void Show()
{
for(int i=0;i<vertexnum-1;i++)
{
cout<<v[i];
if(eage[i][i+1]==1)
{
cout<<" + ";
}
else if(eage[i][i+1]==2)
{
cout<<" * ";
}
}
cout<<v[vertexnum-1];
}

void InitME(vector< vector<int> > &x,int num)
{
switch(num)
{
case 1:
for(int i=0;i<vertexnum;i++)
{
vector<int> temp;
for(int j=0;j<vertexnum;j++)
{
temp.push_back(0);
}
x.push_back(temp);//m初始化
}
return;
case 2:
for(int i=0;i<vertexnum;i++)
{
for(int j=0;j<vertexnum;j++)
{
x[i][j]=0;
}
}
return;
case 3:
for(int i=0;i<vertexnum;i++)
{
int temp;
temp=Random(-5,5);
v.push_back(temp);//顶点随机赋值
for(int j=i+1;j<vertexnum;j++)
{
temp=Random(1,2);//边赋运算随机赋值,1表示+,2表示乘,0表示无效
eage[i][j]=temp;
eage[j][i]=temp;
}
}
return;
};
}

void Move()
{
int temp1=v[0];
int temp2=eage[0][1];
for(int j=1;j<vertexnum-1;j++)
{
v[j-1]=v[j];
eage[j-1][j]=eage[j][j+1];
eage[j][j-1]=eage[j][j+1];
}
v[vertexnum-2]=v[vertexnum-1];
eage[vertexnum-2][vertexnum-1]=eage[vertexnum-1][0];
eage[vertexnum-1][vertexnum-2]=eage[vertexnum-1][0];

v[vertexnum-1]=temp1;
eage[vertexnum-1][0]=temp2;
eage[0][vertexnum-1]=temp2;
}

int Random(int start,int end)
{
int temp=start+rand()%(end-start+1);
if(temp==0)
{
temp=temp+1;
}
return temp;
}

int GetV(int a,int b,int i,int j)
{
switch(eage[i][j])
{
case 0:
return 0;
case 1:
return a+b;
case 2:
return a*b;
}
}

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;k<j;k++)//组内划分,从最下标+1开始,到右下标结束 { int temp=GetV(m[i][k],m[k+1][j],k,k+1); if(temp>m[i][j]) { m[i][j]=temp; } } } } return m[0] ; }

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<iostream>
#include<vector>
#include<cstdlib>
#include<cmath>
using namespace std;

typedef struct P
{
int p;
int bitp;
};
vector<P> p;
int pnum;
vector< vector<int> > 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<p[k].bitp)
{
tempb=p[k].bitp;
}
}
return 11+(j-i+1)*tempb;
}

//链式问题的万金油,基本都可用这段代码,换换一些东西来解
int CountMin()
{
for(int i=0;i<pnum;i++)
{
m[i][i]=CountCost(i,i);
}
int N=pnum-1;
for(int r=2;r<=pnum;r++)
{
for(int i=0;i<=N-r+1;i++)
{
int j=i+r-1;
m[i][j]=CountCost(i,j);
for(int k=i;k<j;k++)
{
int t=m[i][k]+m[k+1][j];
if(t<m[i][j])
{
m[i][j]=t;
}
}
cout<<"m["<<i<<"]["<<j<<"]: "<<m[i][j]<<endl;
}
}
return m[0]
;
}

int main()
{
cin>>pnum;
for(int i=0;i<pnum;i++)
{
P temp;
temp.p=Random(1,255);
temp.bitp=ceil(log(temp.p+1+0.0)/log(2+0.0));
p.push_back(temp);
cout<<"P: "<<temp.p<<" BitP: "<<temp.bitp<<endl;
}
for(int i=0;i<pnum;i++)
{
vector<int> t;
for(int j=0;j<pnum;j++)
{
t.push_back(0);
}
m.push_back(t);
}
cout<<CountMin()<<endl;
return 0;
}

B.书中解法

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



#include<iostream>
#include<vector>
#include<cstdlib>
#include<cmath>
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<int> &x,int n)
{
for(int i=0;i<n;i++)
{
x.push_back(0);
}
}
int CountMin(vector<P> &p,vector<int> &s,vector<int> &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(bmax<p[i-j+1].bitp)//遍历是从靠近i的最左边元素i-1开始的 { bmax=p[i-j+1].bitp; } if(s[i]>s[i-j]+j*bmax)//该段向左扩张,依次比较扩张一个单元,与不扩张该单元的大小 { s[i]=s[i-j]+j*bmax; l[i]=j; } } s[i]+=header; } return s ; }
int main()
{
vector<P> p;//存放像素值,及其存储该像素点所需位数
vector<int> s;//存放0~i的,最小存储所用内存大小
vector<int> l;//存放每段所包含的元素个数
int pnum;//像素点个数
cin>>pnum;
for(int i=0;i<pnum;i++)
{
P temp;
temp.p=Random(1,255);
temp.bitp=ceil(log(temp.p+1+0.0)/log(2+0.0));
p.push_back(temp);
cout<<"P: "<<temp.p<<" BitP: "<<temp.bitp<<endl;
}
InitX(s,pnum);
InitX(l,pnum);
cout<<CountMin(p,s,l)<<endl;
return 0;
}

5.电路布线

(1)当i=1时



(2)当i>1时



#include<iostream>
#include<vector>
#include<map>
#include<cmath>
using namespace std;
int mnset(vector< vector<int> > &m,map<int,int> &c)
{
int N=c.size()-1;
for(int j=0;j<c[1];j++)
{
m[1][j]=0;
}
for(int j=c[1];j<=N;j++)
{
m[1][j]=1;
}
for(int i=2;i<N;i++)
{
for(int j=0;j<c[i];j++)
{
m[i][j]=m[i-1][j];
}
for(int j=c[i];j<=N;j++)
{
m[i][j]=max(m[i-1][j],m[i-1][c[i]-1]+1);
}
}
m

=max(m[N-1]
,m[N-1][c
-1]+1);
return m

;
}
int main()
{
vector< vector<int> > m;
map<int,int> c;
int num;
cout<<"please input the number of wiring terminal"<<endl;
cin>>num;
cout<<"please input c[i]"<<endl;
for(int i=0;i<num;i++)
{
vector<int> temp;
int t;
cin>>t;
for(int j=0;j<num;j++)
{
temp.push_back(0);
}
m.push_back(temp);
c[i]=t;
}
cout<<mnset(m,c)<<endl;
}

6.流水作业调度

A.Johnson法则



解读:

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

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

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

4.ai>bi的工作集,应该排在ai<bi的工作集之后。相反的话,M2一开始会闲置,后来会拥挤,导致后续作业堆积。

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

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

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

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

#include<iostream>
#include<vector>
#include<cstdlib>
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()<b.getKey();
}
int flowShop(vector<int> &a,vector<int> &b,vector<int> &c)
{
int n=a.size();
vector<Node> d;//排序分类辅助数组
//d的key中有ai和bi,bi的d的job标记为true,为N1,ai的d的job标记为false,为N2.按关键字升序排序,再通过遍历筛选,前半段为N1,升序排列,后半段为N2,降序排列
for(int i=0;i<n;i++)
{
int key=a[i]>b[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<n;i++)
{
if(d[i].getJob())
{
c[j++]=d[i].getIndex();
}
else
{
c[k--]=d[i].getIndex();
}
}
j=a[c[0]];
k=j+b[c[0]];
for(int i=1;i<n;i++)
{
j+=a[c[i]];//M1在执行第c[i]号a时,M2在执行第c[i-1]号b。j+为M1执行完a[i]的时间,k为M2执行完b[i-1]的时间
k=j<k?k+b[c[i]]:j+b[c[i]];//谁大,赋谁
}
return k;
}

int main()
{
vector<int> a;
vector<int> b;
vector<int> c;
int num;
cin>>num;
for(int i=0;i<num;i++)
{
int temp;
temp=random(1,100);
a.push_back(temp);
temp=random(1,100);
b.push_back(temp);
c.push_back(0);
}
cout<<flowShop(a,b,c)<<endl;
}

7.最优二叉搜索树

//a[]存储访问不命中概率x=(xi,xi+1),b[]存储各内节点命中概率x=xi。两者之和为1。a[j]表示查到节点j,但是<span style="font-family: Arial, Helvetica, sans-serif;">x=(xj,xj+1)</span><span style="font-family: Arial, Helvetica, sans-serif;">未命中;b[ j ]表示查到节点j,且x=xj命中</span>
//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<float> > &m,vector< vector<float> > &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<n;r++)//起止下标差
{
for(int i=1;i<=n-r;i++)//i起
{
int j=i+r;//j止
w[i][j]=w[i][j-1]+a[j]+b[j];//加上命中x=xi的概率b[j]和不命中的概率x=(xi,xi+1)的概率a[j],计算该树概率总和 ,即访问到该树根节点的概率
//计算左右子树最优期望之和
m[i][j]=m[i+1][j];//初始化i为根节点,无左子树,只有右子树
for(int k=i+1;k<=j;k++)
{
float t=m[i][k-1]+m[k+1][j];//遍历寻找最优搜素二叉树,k为根节点
if(t<m[i][j])
{
m[i][j]=t;
}
}
//加上根节点期望路径长度,得本树最优期望
m[i][j]+=w[i][j];//加上根节点的命中概率*1(访问根节点的平均长度)
}
}
}

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<int> &v,vector<int> &w,int c,vector< vector<int> > &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
-1,c);
for(int j=0;j<=JMax;j++)//在只能选择物品n,且背包大小装不下w
时,背包能装下的最大价值为0
{
m
[j]=0;
}
for(int j=w
;j<=c;j++)//在只能选择物品n,且背包大小装得下w
时,背包能装下的最大价值为v

{
m
[j]=v
;
}
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]);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: