时间、空间复杂度:算法
O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)数组
“最大子列和”则被定义为全部连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。函数
序列子列和属于求最大值问题,优化
求最大值时:this
分治法 经过对序列的分割比较值,只能求出最大值spa
蛮力法 不断地取每一个序列的值,与最大的序列的和的值进行比较,只能求出最大值设计
动态规划 用dp[]记录每一个位置的最大值,同时能够经过dp[]<0输出子序列rest
算法排序 |
求最大值递归 |
输出序列 |
时间复杂度 |
分治 |
√ |
× |
O(nlog2n) |
蛮力 |
√ |
× |
O(n) |
动态规划 |
√ |
√ |
O(n) |
注:输出序列只是针对下列算法过程
此类属于求解组合问题
三个区间:左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);
}
蛮力法优化过程
时间复杂度: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;
}
状态转移方程为:
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]<<" ";
}
有 n 个重量分别为w一、w二、···、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[][] |
√ |
√ |
//可以使用递归求解,本题暂不使用,时间复杂度不变//
/**
* 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++)
//输出选中物品//
··· ···
//输出后大括号、最大重量、最大价值//
··· ···
}
狭义上:回溯=DFS+剪枝;
广义上:带回退的(包括递归)都是回溯算法;
回溯法算法优化过程:
不带剪枝->+带左剪枝->+带右剪枝
无剪枝
//物品数目//
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);
}
}
}
+右剪枝再也不有效,采用上界函数 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);
}
}
}
背包问题此时能够分解物品
三种贪心策略
价值最大物品
重量最轻物品
单位重量下价值最大物品
第三种贪心策略,简单描述过程为
排序
背包余下重量 weight ,初始值为 W,初始价值 V
第 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];
}
}
第 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背包。
有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) |
//可以使用递归求解,本题暂不使用,时间复杂度不变//
//存放成本//
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;
}
}
}
//临时解//
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;
}
}
}
//同回溯法,定义结构相似//
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;
}
}
}
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]的前驱订单
pre[i]=-1; A[i]没有前驱;
pre[i]=-2; A[i]不被选择;
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;
}
}
}
}