時間、空間複雜度:算法
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;
}
}
}
}