回溯法——裝載問題


問題描述:

  有一批共n個集裝箱要裝上2艘載重量分別為c1和c2的輪船,其中集裝箱i的重量是wi,且不能超,即Σwi<=c1+c2。

算法思想:

  ——在給定的裝載問題有解的情況下

  最優裝載方案: 首先將第一艘輪船盡可能的裝滿;  

          然后將剩余的集裝箱裝上第二艘輪船。

  將第一艘輪船盡可能的裝滿等價於選取全體集裝箱的一個子集,使該子集中集裝箱重量之和最接近c1。

算法設計:

  先考慮裝載一艘輪船的情況,依次討論每個集裝箱的裝載情況,共分為兩種,要么裝(1),要么不裝(0),因此很明顯其解空間樹可以用子集樹來表示。

  在算法Maxloading中,返回不超過c的最大子集和,但是並沒有給出到達這個最大子集和的相應子集,稍后完善

  在算法Maxloading中,調用遞歸函數Backtrack(1)實現回溯搜索。Backtrack(i)搜索子集樹中的第i層子樹。

  在算法Backtrack中,當i>n時,算法搜索到葉結點,其相應的載重量為cw,如果cw>bestw,則表示當前解優於當前的最優解,此時應該更新bestw。

  算法Backtrack動態地生成問題的解空間樹。在每個結點處算法花費O(1)時間。子集樹中結點個數為O(2^n),故Backtrack所需的時間為O(2^n)。另外Backtrack還需要額外的O(n)的遞歸棧空間。

算法描述:

 1 template <class Type>
 2 class Loading
 3 {
 4     friend Type MaxLoading(Type [],Type,int);
 5     private:
 6         void Backtrack(int i);
 7         int n;        //集裝箱數目  8         Type * w,      //集裝箱重量數組  9                c,      //第一艘輪船的載重量 10                cw,      //當前載重量 11                bestw;    //當前最優載重量 12 };
13 
14 template <class Type>
15 void Loading<Type>::Backtrack(int i)  //回溯搜索 16 {  //搜索第i層結點 17     if(i>n)    //到達葉結點 18     {
19         if(cw>bestw)
20             bestw = cw;
21         return;
22     }
23     if(cw+w[i] <= c)  //搜索子樹 24     {
25         cw += w[i];  //當前載重量增加正考慮對象的重量 26         Backtrack(i+1); 27         cw -= w[i];    //遞歸返回上一層時,記得減去剛考慮對象的集裝箱重量 28     }
29     Backtrack(i+1);  //遞歸搜索第i+1層 30 }
31 
32 template <class Type>
33 Type MaxLoading(Type w[],Type c,int n)  //返回最優載重量 34 {
35     Loading<Type> X;  //初始化 36     X.w = w;
37     X.c = c;
38     X.n = n;
39     X.bestw = 0;    //當前最優載重量的初值賦為0 40     X.cw = 0;
41     X.Backtrack(1);  //計算最優載重量————調用遞歸函數,實現回溯搜索 42     return X.bestw;
43 }

上界函數:

  引入剪枝函數,用於剪去不含最優解的子樹:即當cw(當前載重量)+r(未考察對象的總重量)<bestw(當前的最優載重量)時當前子樹不可能包含最優解,直接減掉。

 1 template <class Type>
 2 class Loading
 3 {
 4     friend Type MaxLoading(Type [],Type,int);
 5     private:
 6         void Backtrack(int i);
 7         int n;
 8         Type * w,
 9             c,
10             cw,
11             bestw,
12             r;//剩余集裝箱重量————未考察過的集裝箱的重量,並非沒有裝載的集裝箱重量
13 };
14 template <class Type>
15 void Loading<Type>::Backtrack(int i)
16 {
17     if(i>n)
18     {
19         if(cw>bestw)
20             bestw = cw;
21         return;
22     }
23     r-=w[i];    //計算剩余(未考察)的集裝箱的重量,減去當前考察過的對象的重量
24     if(cw+w[i] <= c)
25     {
26         cw += w[i];
27         Backtrack(i+1);
28         cw -= w[i];
29     }
30     Backtrack(i+1);
31     r+=w[i];    //遞歸回退返回上一層時,記得修改r的當前值,如果得不到最優解,再取消當前考察的集裝箱,標記為未選,因此剩余容量要再加上當前集裝箱重量
32 }
33 template <class Type>
34 Type MaxLoading(Type w[],Type c,int n)
35 {
36     Loading<Type> X;   //初始化 37     X.w = w;
38     X.c = c;
39     X.n = n;
40     X.bestw = 0;
41     X.cw = 0;
42     X.r = 0;  //初始化r 43     for(int i=1;i<=n;i++)  //計算總共的剩余(當前為考察過的)集裝箱重量
44         X.r += w[i];
45     X.Backtrack(1);
46     return X.bestw;
47 }

構造最優解:

   為了構造最優解,必須在算法中保存最優解的記錄。因此需要兩個成員數組 x ,bestx,一個用於記錄當前的選擇,一個用於記錄最優記錄

改進后的算法描述如下:

 1 template <class Type>
 2 class Loading
 3 {
 4     friend Type MaxLoading(Type [],Type,int);
 5     private:
 6         void Backtrack(int i);
 7         int n,
 8             * x,     //當前解  9             * bestx;   //當前最優解 10         Type * w,    //集裝箱重量數組 11             c,      //第一艘輪船的載重量 12             cw,      //當前載重量 13             bestw,    //當前最優載重量 14             r;    //剩余集裝箱重量————未考慮過的集裝箱重量,並非沒有裝載的集裝箱重量
15 };
16 template <class Type>
17 void Loading<Type>::Backtrack(int i)
18 {  //搜索第i層結點 19     if(i>n)  //到達葉子結點 20     {
21         if(cw>bestw)
22         {
23             for(j=1;j<=n;j++)
24                 bestx[j] = x[j];
25             bestw = cw;
26         }
27         return;
28     }  //搜索子樹 29     r-=w[i];  //計算剩余(未考慮過)集裝箱的重量,減去當前考慮過的集裝箱重量
30     if(cw+w[i] <= c)  //搜索左子樹 31     {
32         x[i] =1;
33         cw += w[i];
34         Backtrack(i+1);
35         cw -= w[i];
36     }
37     if(cw+r > bestw)  //搜索右子樹——————剪枝函數 38     {
39         x[i] = 0;
40         Backtrack(i+1);
41     }
42     r+=w[i];  //遞歸返回上一層時,記得修改r的值,如果取不到最優解,再取消當前考慮的集裝箱,標記為未選,因此剩余容量要再加上當前集裝箱重量
43 }
44 template <class Type>
45 Type MaxLoading(Type w[],Type c,int n)
46 {
47     Loading<Type> X;
48     X.w = w;
49     X.c = c;
50     X.n = n;
51     X.bestx = bestx;
52     X.bestw = 0;
53     X.cw = 0;
54     X.r = 0;
55     for(int i=1;i<=n;i++)  //計算總共的剩余(當前為考察過的)集裝箱重量 
56 X.r += w[i]; 57 X.Backtrack(1); 58 delete []X,x; 59 return X.bestw; 60 }

構造最優解的另一種經典方法:

 1 void backtrack(int t)
 2 {
 3     if(t>n)
 4         output(x);
 5     else
 6         {
 7             for(i-0;i<=1;i++)
 8             {
 9                 x[t]=i;
10                 if(cw+i*wt<=c)
11                 {
12                     cw=cw+i*wt;
13                     backtrack(t+1);
14                 }
15             }
16         } 
17 }                

迭代回溯方式:

  利用數組x所含的信息,可將上面方法表示成非遞歸的形式。省去O(n)遞歸棧空間非遞歸迭代回溯法描述如下:

 1 template <class Type>
 2 Type MaxLoading(Type w[],Type c,int n,int bestx[])
 3 {
 4     //迭代回溯法,返回最優裝載量及其相應解,初始化根節點
 5     int i =1;    //當前層 x[1:i-1]為當前路徑  6     int *x = new int[n+1];
 7     Type bestw = 0,  //當前最優載重量  8         cw = 0,    //當前載重量  9         r = 0;    //剩余(未考慮過的)集裝箱重量 10     for(int j=1;j<=n;j++)
11         r+=w[j];
12     while(true)
13     {  //進入子樹 14         while(i<=n && cw+w[i]<=c)
15         {  //搜索左子樹 16             r -= w[i];
17             cw +=w[i];
18             x[i] =1;
19             i++;
20         }
21         if(i>n)  //到達葉子結點 22         {
23             for(int j=1;j<=n;j++)
24                 bestx[j] = x[j];
25             bestw = cw;
26         }
27         else  //進入右子樹
28         {
29             r -= w[i];
30             x[i] = 0;
31             i++;
32         }
33         while(cw+w[i] <= bestw)
34         {  //剪枝回溯 35             i--;
36             while(i>0 && !x[i])
37             {  //從右子樹返回 38                 r+=w[i];
39                 i--;
40             }
41             if(i == 0)
42             {
43                 delete[] x;
44                 return bestw;
45             }   //進入右子樹 46             x[i] =0;
47             cw -= w[i];
48             i++;
49         }
50     }
51 }

 注:上面的解法實際上就是針對一艘輪船的最優轉載

        針對於多艘輪船就不一定是最優解了。。。。。

 如果是多艘輪船的裝載問題: 

  同樣選擇回溯法解決——子集樹,只是每個集裝箱的選擇范圍不再僅僅是裝否兩種考慮情況,而變為不裝抑或是裝載到某個編號的輪船上等多種考慮范圍

例如:n=3,c1=c2=50,w=[10,40,40],所以對於3個集裝箱中的任何一個都有3種選擇不裝、轉載到1號輪船和轉載到2號輪船。

基本框架如下:

1 void backtrack (int t)        //t:代表待考察的對象
2 {
3   if (t>n) output(x);        //n:考察對象的個數
4     else
5       for (int i=0;i<=2;i++) {    //控制分支的數目,此處應有3個分支,0、1、2分別代表不裝,裝載到1號輪船,裝載到2號輪船
6         x[t]=i;
7         if (constraint(t)&&bound(t)) backtrack(t+1);    //剪枝函數:約束函數+限界函數   ————>  遞歸
8       }
9 }

約束函數為:

     cw1+wt*i/i<=c1&&cw2+wt*i/i<=c2;

具體實現為

 1 void backtrack (int t)        //t:代表待考察的對象
 2 {
 3   if (t>n) output(x);        //n:考察對象的個數
 4     else
 5       for (int i=0;i<=2;i++) {    //控制分支的數目,此處應有3個分支,0、1、2分別代表不裝,裝載到1號輪船,裝載到2號輪船  6             x[t]=i;
 7             //剪枝函數:約束函數+限界函數   ————>  遞歸    
 8             if (i==1? cw1+wt*i/i<=c1:cw2+wt*i/i<=c2) {  //滿足每艘輪船的載重要求,小於其載重量  9                 if(i==1) cw1=cw1+i*wt/i;    //當前載重的改變 10                 if(i==2) cw2=cw2+i*wt/i;  
11                 backtrack(t+1);
12             }
13       }
14 }
15    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM