算法設計與分析 - 題型分析 - 第 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。現要求你編寫程序,計算給定整數序列的最大子列和。函數

 

分析

序列子列和屬於求最大值問題,優化

求最大值時: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]<<" ";

}

0-2 0/1揹包及揹包問題

問題示例

有 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[][]

 

 

解法

  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;

}

}

}

 

三、動態規劃

資源分配問題

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;

}

}

}

}

相關文章
相關標籤/搜索