算法设计与分析 - 题型分析 - 第 1 弹

算法前提

时间、空间复杂度:

O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)

0-1 最大子列和问题

问题示例

“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。

 

分析

序列子列和属于求最大值问题,

求最大值时:

分治法 通过对序列的分割比较值,只能求出最大值

蛮力法 不断地取每个序列的值,与最大的序列的和的值进行比较,只能求出最大值

动态规划 用dp[]记录每个位置的最大值,同时可以通过dp[]<0输出子序列

 

算法

求最大值

输出序列

时间复杂度

分治

×

O(nlog2n)

蛮力

×

O(n)

动态规划

O(n)

注:输出序列只是针对下列算法过程

解法

1、分治法

此类属于求解组合问题

三个区间:左a、右b、中间偏左+中间偏右c

所求值为:max(三个区间)

 

同上,递归过程为:

求最大值()=aa[n] n=1

求最大值()=max(求最大值(aa[],左,mid),求最大值(aa[],mid,右),mid->左最大值+mid->右最大值) n>1

 

算法如下

max(三个区间最大值a,b,c)

{

if(a<b) a=b;

if(a>c) return a;

else return c;

}

//递归求每一组最大值//

int 求最大值(存序列的数组aa[],最左位数,最右位数)

{

int i,j;

int 三个区间的最大值a,b,c;

int 第三个区间的暂存值temp;

//初始化第三个区间的值为0//

。。。

if(左==右)

{

if(aa[左]>0)

return aa[左];

else

return 0;

}

int mid;

a=求最大值(aa[],最左,mid);

b=求最大值(aa[],mid,最右);

for(i=mid;i>=最左;i++)

{

temp偏左+=aa[i];

if(temp偏左>c偏左)

c偏左=temp偏左;

}

for(i=mid;i<=最右;i++)

{

temp偏右+=aa[i];

if(temp偏右>c偏右)

c偏右=temp偏右;

}

return max(a,b,c);

}

int main()

{

输入aa[];

n=aa[]大小;

输出求最大值(aa[],0,n-1);

}

2、蛮力法

蛮力法优化过程

时间复杂度:O(n3)->O(n2)->O(n)

 

三层循环过程

int 求最大值(aa[],n)

{

int i,j,k;

int thisSum,maxSum=0;

for(i=0;i<n;i++)

for(j=0;j<n;j++)

{

thisSum=0;

for(k=0;k<n;k++)

thisSum+=aa[k];

if(thisSum>maxSum)

maxSum=thisSum;

}

return maxSum;

}

二层循环过程

int 求最大值(aa[],n)

{

int i,j;

int maxSum=0,thisSum;

for(i=0;i<n;i++)

{

thisSum=0;

for(j=0;j<n;j++)

{

thisSum+=aa[j];

if(thisSum>maxSum)

maxSum=thisSum;

}

}

return maxSum;

}

一层循环过程

int 求最大值(aa[],n)

{

int i;

int maxSum=0,thisSum=0;

for(i=0;i<n;i++)

{

thisSum+=aa[i];

if(thisSum<0)

thisSum=0;

if(thisSum>maxSum)

maxSum=thisSum;

}

return maxSum;

}

3、动态规划

状态转移方程为:

dp[0]=0; 边界条件

dp[j]=max{dp[j-1]+aaj,aaj} 1<=j<=n

 

一版情况将aa[]和dp[]设置为全局变量,此处假使已经设置为全局变量。且aa[0]=0;

void 求最大值()

{

dp[0]=0;

for(j=1;j<+n;j++)

dp[j]=max(dp[j-1]+aa[j],aa[j]);

}

void 输出最大值()

{

int maxj=1;

int i,k;

//求出最大值dp[maxj]//

for(j=2;j<=n;j++)

if(dp[j]>dp[maxj])

maxj=j;

//求出子序列的起始位置//

for(i=maxj;i>=1;i++)

if(dp[i]<0)

break;

//输出子序列//

for(k=i+1;k<=maxj;k++)

cout<<a[k]<<" ";

}

0-2 0/1背包及背包问题

问题示例

有 n 个重量分别为w1、w2、···、wn的物品(物品编号为 1 ~ n ),它们的价值分别为 v1 、 v2 、 ··· 、 vn ,给定一个容量为 W 的背包。设计从这些物品中选取一部分物品放入该背包的方案,要求每个物品要么选中要么不选中,要求选中的物品不仅能够放入到背包中,而且具有最大的价值,并对表中的 4 个物品求出 W = 6 时的所有解和最佳解。

 

物品编号

重量

价值

1

5

4

2

3

4

3

2

3

4

1

1

 

 

分析

复杂度

 

 

算法

最优时间复杂度

花费时间地方

存储值

存储结果

蛮力法

O(2n)

求幂集

回溯法

最坏O(2n)

剪枝

贪心法

O(nlog2n)

排序

动态规划

O(nW)

求dp[][]

 

 

解法

  1. 蛮力法

0/1背包问题

前提:求解幂集

//可使用递归求解,本题暂不使用,时间复杂度不变//

 

/**

* vector<vector<int > > ps;

* vector<vector<int> > ps1;

* vector<int> s;

*/

vector<vector<int> >ps;

void 求解幂集(int n)

{

vector<vector<int> >ps1;

vector<vector<int> >::iterator it;

vector<int> s;

ps.push_back(s);

for(int i=1;i<=n;i++)

{

ps1=ps;

for(it=ps1.begin();it!=ps1.end();it++)

(*it).push_back(i);

for(it=ps1.begin();it!=ps1.end();it++)

ps.push_back(*it);

}

}

蛮力求解最佳方案

void 求解最佳方案(int w[],int v[],int W)

{

//计数,序号初始化为1//

int count=1;

//总重量、总价值//

int sumw,sumv;

//最大重量、最大价值初始为0//

int i,maxsumw=0,maxsumv=0;

vector<vector<int> >::iterator it;

vector<int>::iterator sit;

//输出格式:序号、选中物品、总重量、总价值、能否装入//

··· ···

for(it=ps.begin();it!=ps.end;it++)

{

//输出序号//

··· ···

sumw=sumv=0;

//输出前大括号//

··· ···

for(sit=(*it).begin();sit!=(*it).end();sit++)

{

//输出序号//

··· ···

sumw+=w[*sit-1];

sumv+=v[*sit-1];

}

//输出后大括号、总重量、总价值//

if(sumw<=W)

{

//输出能够装入//

··· ···

if(sumv>maxsumv)

{

maxsumw=sumw;

maxsumv=sumv;

//标记序号//

maxi=count;

}

}

else

//输出不能装入//

··· ···

count++;

}

//输出最佳方案、选中物品、左大括号//

··· ···

//输出标记位置的物品//

for(sit=ps[maxi].begin();sit!=ps[maxi].begin();sit++)

//输出选中物品//

··· ···

//输出后大括号、最大重量、最大价值//

··· ···

}

  1. 回溯法

0/1背包问题

狭义上:回溯=DFS+剪枝;

广义上:带回退的(包括递归)都是回溯算法;

 

情况1:装入背包中物品重量和恰好为W

 

回溯法算法优化过程:

不带剪枝->+带左剪枝->+带右剪枝

 

无剪枝

//物品数目//

int n;

//存放最优解//

int x[MAXN];

//最优解的总价值,初始化为0//

int maxv=0;

//参数按照顺序格式为:第 i 个物品、装入背包物品总重量、装入背包物品总价值、一个解向量op[]//

void dfs(int i,int tw,int tv,int op[])

{

if ( i > n )

{

if ( tw == W && tv > maxv )

{

maxv=tv;

for(int j=1;j<=n;j++)

x[j] = op [j];

}

}

else

{

op[i]=1;

dfs(i+1,tw+w[i],tv+v[i],op);

op[i]=0;

dfs(i+1,tw,tv,op);

}

}

 

+左剪枝

void dfs(int i,int tw,int tv,int op[])

{

if(i>n)

{

if(tw == W&&tv>maxv)

{

maxv=tv;

for(j=1;j<=n;j++)

x[j]=op[j];

}

}

else

{

if(tw+w[i]<W)

{

op[i]=1;

dfs(i+1,tw+w[i],tv+v[i],op);

}

op[i]=0;

dfs(i+1,tw,tv,op);

}

}

 

+右剪枝

//添加参数rw:所有物品重量和//

void dfs(int i,int tw,int tv,int rw,int op)

{

if(i>n)

{

if(tw==W&&tv>maxv)

{

for(int j=1;j<=n;j++)

x[j]=op[j];

}

}

else

{

if(tw+w[i]<=W)

{

op[i]=1;

dfs(i+1,tw+w[i],tv+v[i],rw-w[i],op);

}

//能不能满足界限 W //

if(tw+rw-w[i]>=W)

{

op[i]=0;

dfs(i+1,tw,tv,rw-w[i],op);

}

}

}

 

情况2:装入背包中物品重量和不超过W

+右剪枝不再有效,采用上界函数 bound 进行右剪枝

 

//数组存放物品,成员顺序依次为序号、重量、价值、单位重量价值//也可按照结构数组定义,如贪心法中数组//

class TH

{

public:

int no;

int w;

int v;

double p;

}

 

//上界判定函数//

int bound(int i,int tw,tv)

{

i++;

while(i<=n && tw+A[i].w<=W)

{

tw+=A[i].w;

tv+=A[i].v;

i++;

}

if(i<=n)

//第 i 个物品不能整个放入//

return tv+(W-tw)*A[i].p;

else

return tv;

}

 

//dfs//

void dfs(int i,int tw,int tv,int op[])

{

if(i>n)

{

maxv=tv;

for(int j=1;j<=n;j++)

x[j]=op[j];

}

else

{

if(tw+A[i].w<=W)

{

op[i]=1;

dfs(i+1,tw+A[i].w,tv+A[i].v,op);

}

//上界函数确定是否剪枝//

if(bound(i,tw,tv)>maxv)

{

op[i]=0;

dfs(i+1,tw,tv,op);

}

}

}

  1. 贪心法

背包问题

背包问题此时可以分解物品

三种贪心策略

  1. 价值最大物品

  2. 重量最轻物品

  3. 单位重量下价值最大物品

 

第三种贪心策略,简单描述过程为

  1. 排序

  2. 背包余下重量 weight ,初始值为 W,初始价值 V

  3. 第 i 个装入背包,直到能够装入最后的一件物品

 

算法过程如下

double W;

double V;

//物品装入的数量:0~1//

double x[MAXN];

//存放物品的结构数组定义,结构定义顺序为重量、价值、单位重量价值、重载 < 按照单位重量价值递减排序//

struct NodeType

{

double w;

double v;

double p;

bool operator < (const NodeType &s) const

{

return p>s.p;

}

}

 

void 求最大价值()

{

V=0;

double weight =W;

memset(x,0,sizeof(x));

int i=1;

while(A[i].w<weight)

{

x[i]=1;

weight-=A[i].w;

V+=A[i].v;

i++;

}

if(weight>0)

{

x[i]=weight/A[i].w;

V+=x[i]*A[i].v;

 

}

}

最优装载问题

排序上花费时间较多

 

//最优解向量//

int x[MAXN];

//最优解总重量//

int maxw;

//总重量//

int W;

 

//求解//

void 最优装载()

{

memset(x,0,sizeof(x));

sort(w+1,w+n+1);

maxw=0;

int restw=W;

for(int i=1;i<=n&& w[i]<=restw;i++)

{

x[i]=1;

restw-=w[i];

maxw+=w[i];

}

}

  1. 动态规划

0/1背包问题

第 i 个物品,背包剩余容量 r,dp[i][r]最优价值

 

状态转移方程:

dp[i][0]=0; 边界条件dp[i][0]=0(1<=i<=n);

dp[0][r]=0; 边界条件dp[0][r]=0(1<=r<=W);

dp[i][r]=dp[i-1][r]; 当r<w[i]时物品i放不下;

dp[i][r]=max(dp[i-1][r],dp[i-1][r-w[i]]+v[i]); 否则在不放入和放入第 i 个物品之间选择最优解;

 

决策是否装入时 x[] 存在两种状态:

x[i]=0; 不装入;

x[i]=1;此时 r = r -w[i];maxv=maxv+v[i]; 装入;

 

求出dp[][],然后进行回推。自底向上的求解过程。

 

算法如下:

//最大价值//

int dp[MAXN][MAXW];

//是否选取//

int x[MAXN];

//最大价值//

int maxv;

 

//求解//

void 求dp[][]()

{

int i,r;

for(i=0;i<=n;i++)

dp[i][0]=0;

for(r=0;r<=W;r++)

dp[0][r]=0;

for(i=1;i<=n;i++)

{

for(r=1;r<=W;r++)

//w[i]能否放入//

if(r<w[i])

dp[i][r]=dp[i-1][r];

else

dp[i][r]=max(dp[i-1][r],dp[i-1][r-w[i]]+v[i]);

}

}

 

//回推求解最优解//

void 选定物品()

{

int i=n,r=w;

maxv=0;

while(i>=0)

{

//对最后一个状态转移方程进行回推//

if(dp[i][r]!=dp[i-1][r])

{

x[i]=1;

maxv+=v[i];

r-=w[i];

}

else

x[i]=0;

i--;

}

}

完全背包问题

动态规划优化过程:

O(nW2)->O(nW)

 

O(nW2)

第 i 个物品,重量不超过j,dp[i][j]最大总价值

fk[i][j]得到最大值时物品 i 挑选的件数

 

同上,状态转移方程:

dp[i][0]=0; 背包不能装入任何物品时总价值为0;

dp[0][j]=0; 没有任何物品装入时总价值为0;

dp[i][j]=max{dp[i-1][j-k*w[i]]+k*v[i]}; 当dp[i][j]<dp[i-1][j-k*w[i]]+k*v[i](k*w[i]<=j)时

fk[i][j]=k; 物品 i 取 k 件;

 

算法过程如下:

 

int fk[MAXN+1][MAXW+1];

int dp[MAXN+1][MAXW+1];

 

//求解//

int 求解dp[][]()

{

int i,j,k;

for(i=1;i<=n;i++)

for(j=0;j<=w;j++)

for(k=0;k*w[i]<=j;k++)

{

if(dp[i][j]<dp[i-1][j-k*w[i]]+k*v[i])

{

dp[i][j]=dp[i-1][j-k*w[i]]+k*v[i];

fk[i][j]=k;

}

}

return dp[n][W];

}

 

//回推求解最优解//

void 回推()

{

int j=n,j=W;

while(i>=1)

{

cout<<i<<" "<<fk[i][j];

j-=fk[i][j]*w[i];

i--;

}

}

 

O(nW)

状态转移方程:

dp[i][j]=dp[i-1][j]; 当j<w[i]时物品放不下;

dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i];

 

//求解//

int 求解dp[][]()

{

int i,j;

for(i=1;i<=n;i++)

for(j=0;j<=W;j++)

{

if(j<w[i])

dp[i][j]=dp[i-1][j];

else

dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);

}

return dp[n][W];

}

此时回推同0/1背包。

 

0-3 求解任务分配问题

问题示例

有n(n>=1)个任务需要分配给n个人执行,每个任务只能分配给一个人,每个人只能执行一个任务,第 i 个人执行第 j 个任务的成本时c[i][j](1<=i,j<=n)。求出总成本最小的一种分配方案。

分析

复杂度

 

 

算法

时间复杂度

方案输出

最优值输出

蛮力法

任务分配

O(n×n!)

回溯法

任务分配

O(nn)

活动安排

O(n!)

动态规划

资源分配

O(m×n2)

会议安排

O(nlog2n)

 

 

解法

  1. 蛮力法

任务分配问题

前提:求解全排列

//可使用递归求解,本题暂不使用,时间复杂度不变//

 

//存放成本//

int c[MAXN][MAXN];

//存放全排列//

vector<vector<int> > ps;

 

//插入 i 使得集合增加//

void insert(vector<int> s,int i,vector<vector<int> > &ps1)

{

vector<int> s1;

vector<int>::iterator it;

for(int j=0;j<i;j++)

{

s1=s;

it=s1.begin()+j;

s1.insert(it,i);

ps1.push_back(s1);

}

}

 

//求解全排列//

void 求解全排列()

{

vector<vector<int > > ps1;

vector<vector<int > >::iterator it;

vector<int> s,s1;

s.push_back(1);

ps.push_back(s);

for(int i=2;i<=n;i++)

{

ps1.clear();

for(it=ps.begin();it!=ps.end();it++)

//逐项插入元素//

insert(*it,i,ps1);

ps=ps1;

}

}

 

蛮力法求解最优方案

//蛮力法求解最佳方案//

void 求最佳方案(int n,int &mini,int &minc)

{

求解全排列();

for(int i=0;i<ps.size();i++)

{

int cost=0;

for(int j=0;j<ps[i].size();j++)

cost+=c[i][ps[i][j]-1];

if(cost<minc)

{

minc=cost;

mini=i;

}

}

}

 

  1. 回溯法

任务分配问题

//临时解//

int x[MAXN];

//临时解的成本,初始化为0//

int cost=0;

//存放最优解//

int bestx[MAXN];

//最优解的成本,初始化为INF,INF为#define INF 99999//

int mincost = INF;

//表明任务 j 是否已经分配人员//

bool worker[MAXN];

 

//为第 i 个人分配任务//

void dfs(int i)

{

if(i>n)

{

if(cost<mincost)

{

mincost=cost;

for(int j=1;j<=n;j++)

bestx[j]=x[j];

}

}

else

{

//为人员 i 进行试分配任务 j //

for(int j=1;j<=n;j++)

if(!worker[j])

{

worker[j]=true;

x[i]=j;

cost+=c[i][j];

dfs(i+1);

worker[j]=false;

x[i]=0;

cost-=c[i][j];

}

 

}

}

活动安排问题

有着开始时间 bi 和结束时间 ei

struct Action{

{

int b;

int e;

};

 

int n;

//临时解向量//

int x[MAX];

//最优解向量//

int bestx[MAX];

//活动最大结束时间,初始化为0//

int laste=0;

//所有兼容活动个数,初始化为0//

int sum=0;

//最有调度方案中所兼容活动的个数,初始化为0//

int maxsum=0;

 

//交换数值 x 和 y //

void swap(int &x,int &y)

{

int tem=x;

x=y;

y=tmp;

}

 

//搜索最优解//

void  dfs(int i)

{

if(i>n)

{

if(sum>maxsum)

{

maxsum=sum;

for(k=1;k<=n;k++)

bestx[k]=x[k];

}

}

else

{

for(int j=i;j<=n;j++)

{

swap(x[i],x[j]);

 

int sum1=sum;

int laste1=laste;

 

if(A[x[j]].b>laste)

{

sum++;

laste=A[x[j]].e;

}

dfs(i+1);

swap(x[i],x[j]);

 

sum=sum1;

laste=laste1;

}

}

}

  1. 贪心法

活动安排问题

//同回溯法,定义结构类似//

struct Action

{

int b;

int e;

bool operator<(const Action &s) const

{

//递增排序//

return e<=s.e;

}

}

 

Action A[];

bool flag[MAX];

int Count=0;

 

//求解最大兼容性子集//

void 求解子集()

{

memset(flag,0,sizeof(flag));

sort(A+1,A+n+1);

//前一个兼容活动的结束时间//

int preend=0;

for(int i=1;i<=n;i++)

{

if(A[i].b>=preend)

{

flag[i]=true;

preend=A[i].e;

}

}

}

 

3、动态规划

资源分配问题

n 个物品,分配至 m ,dp[i][s]为 i~m 并分配 s 个物品的获利,dnum[i][s]为dp[i][s]时对应 i 获得的物品

 

状态转移方程:

dp[m+!][j]=0; 边界条件

dp[i][s]=max(v[i][j]+dp[i+1][s-j]; pnum[i][s]=dp[i][s]取最大值的j(0<=j<=n);

 

由pnum反推 i 物品数

 

 

算法过程如下:

//获利情况//

int v[MAXM][MAXM];

//dp//

int dp[MAXM][MAXM];

//分配人数//

int pnum[MAXM][MAXM];

int m,n;

 

//求最优方案dp[][]//

void 求最优()

{

//参数为m获利最优、分配人数//

int maxf,maxj;

for(int j=0;j<=n;j++)

dp[m+1][j]=0;

for(int i=m;i>=1;i--)

for(int s=1;s<=n;s++)

{

maxf=0;

maxj=0;

for(j=0;j<=s;j++)

{

if((v[i][j]+dp[i+1][s-j])>maxf)

{

maxf=v[i][j]+dp[i+1][s-j];

maxj=j;

}

}

dp[i][s]=maxf;

pnum[i][s]=maxj;

}

}

会议安排问题

dp[i]订单中兼容订单的最长时间

状态转移方程:

dp[0]=订单0的时间;

dp[i]=max{dp[i-1],dp[j]+A[i].length}; 订单 j 是结束时间早于订单 i 开始时间的最晚的订单

 

pre[i]是dp[i]的前驱订单

  1. pre[i]=-1; A[i]没有前驱;

  2. pre[i]=-2; A[i]不被选择;

  3. pre[i]=j; A[i]前面最晚的订单A[j];

 

struct NodeType

{

int b;

int e;

int length;

bool operator<(const NodeType t) const

{

//递增排序//

return e<t.e;

}

};

 

int n;

NodeType A[MAX];

int dp[MAX];

int pre[MAX];

 

void 求dp[]和pre[]()

{

memset(dp,0,sizeof(dp);

stable_sort(A,A+n);

dp[0]=A[0].length;

 

pre[0]=-1;

 

for(int i=1;i<=n;i++)

{

int low=0,high=i-1;

//二分法查找确定最晚订单A[low-1]//

while(low<=high)

{

int mid=(low+high)/2;

if(A[mid].e<=A[i].b)

low=mid+1;

else

high=mid-1;

}

//最小订单//

if(low==0)

{

if(dp[i-1]>=A[i].length)

{

dp[i]=dp[i-1];

pre[i]=-2;

}

else

{

dp[i]=A[i].length;

pre[i]=-1;

}

}

else

//状态转移方程,max//

{

if(dp[i-1]>=dp[low-1]+A[i].length)

{

dp[i]=dp[i-1];

pre[i]=-2;

}

else

{

dp[i]=dp[low-1]+A[i].length;

pre[i]=low-1;

}

}

}

}

 

猜你喜欢

转载自www.cnblogs.com/JUSTDOINS/p/12084484.html