一.問題描述
電路板排列問題是大規模電子系統設計中提出的實際問題.
該問題是: 將n塊電路板以最佳排列方案插入帶有n個插槽的機箱中. n塊電路板的不同的排列方式對應於不同的電路板插入方案.
將n塊電路板以最佳排列方式插入帶有n個插槽的機箱中。n塊電路板的不同排列方式對應於不同的電路板插入方案。設B={1, 2, …, n}是n塊電路板的集合,L={N1, N2, …, Nm}是連接這n塊電路板中若干電路板的m個連接塊。Ni是B的一個子集,且Ni中的電路板用同一條導線連接在一起。設x表示n塊電路板的一個排列,即在機箱的第i個插槽中插入的電路板編號是x[i]。
x所確定的電路板排列Density (x)密度定義為跨越相鄰電路板插槽的最大連線數。
如圖,設n=8, m=5,給定n塊電路板及其m個連接塊:
B={1, 2, 3, 4, 5, 6, 7, 8},N1={4, 5, 6},N2={2, 3},N3={1, 3},N4={3, 6},N5={7, 8};其中兩個可能的排列如圖所示,則該電路板排列的密度分別是2,5。
比如:
對於第一幅圖,可以看到跨越插槽2,3的有N2,N3兩根導線(注意:一個連接塊上的電路板由一根導線連接而成)
跨越插槽4,5的有N4,N1兩根導線,同理可得跨越插槽5,6的也是N4,N1兩根導線.
其余所有的相鄰兩個插槽都只跨越了一根導線,
故最終得到的密度就為2.
對於第二幅圖,跨越插槽4,5的有N1,N2,N3,N4,N5五條導線.
跨越插槽3,4的有N3,N4,N1,N5四條導線,其它的就不舉例了.最終密度為5
這樣大家對於密度的定義是不是有了清晰的理解.
二.解題思路
電路板排列問題是NP難問題,因此不大可能找到解此問題的多項式時間算法。
考慮采用回溯法系統的搜索問題解空間的排列樹,找出電路板的最佳排列。
設用數組B表示輸入。
B[i].[j]的值為1當且僅當電路板i在連接塊Nj中。設total[j]是連接塊Nj中的電路板數。對於電路板的部分排列x[1:i],設now[j]是x[1:i]中所包含的Nj中的電路板數。
由此可知,連接塊Nj的連線跨越插槽i和i+1當且僅當now[j]>0且now[j]!= total[j]。用這個條件來計算插槽i和i+1間的連線密度。
划重點!!!
對於這個條件的理解至關重要,我想了很久.
我們可以拿上面第一幅圖來看:
先看now[j]>0,(now[j]表示的是x[1:i]中所包含的Nj的電路板數),now[j]大於零表示前面的i個插槽中有連接塊j的電路板存在,很好理解.
now[j] != total[j]這里,如果當前面的i個插槽中已經用去了連接塊j中的所有電路板,那么就是說剩下的插槽不可能再有連接塊j的電路板了,也就不可能再被跨越!
同樣的,一個插槽最多被一條導線跨越一次,比如上圖1中,對於5,6號插槽來說,N1只跨越5,6插槽一次!故最大我們能夠得到的密度為m,也就是每根導線都跨越了某個相鄰槽,比如圖2.
代碼如下:
// 電路板排列問題
#include<bits/stdc++.h>
using namespace std;
class Board
{
friend int Arrangement(int **, int, int, int*);
private:
void Backtrack(int i, int cd);
int n, //電路板數
m, //連接塊數
*x, //當前解
*bestx, //當前最優解
bestd, //當前最優密度
*total, //total[j]為連接塊j的電路板數
*now, //now[j]為當前解中所含連接塊j的電路板數
**B; //連接塊數組
};
void Board::Backtrack(int i, int cd) //回溯搜索排列樹,cd表示已經確定的x[1:i]個插槽中相鄰兩個插槽被跨越數最大的(就是密度)
{
static int k = 1;
if(i == n) //由於算法僅完成那些比當前最優解更好的排列,故cd肯定優於bestd,直接更新
{
cout<<"當前已經確定下來最后一個插槽,我們選擇"<<x[n]<<endl;
cout<<"第"<<k++<<"個方案為: ";
for(int j=1; j<=n; j++)
{
bestx[j] = x[j];
cout<<x[j]<<" ";
}
bestd = cd;
cout<<"獲得的密度為: "<<bestd<<endl<<"到達最后一層,回溯一層到達第"<<n-1<<"層"<<endl;
}
else
{
for(int j=i; j<=n; j++) //選擇x[j]為下一塊電路板
{
int ld = 0; //新的排列部分密度
for(int k=1; k<=m; k++) //遍歷連接塊1~m,並且計算得到跨越插槽i和i+1的導線數ld
{
now[k] += B[x[j]][k];
if(now[k]>0 && total[k]!=now[k]) //判斷是否發生了跨越(左邊有,右邊也有)
ld++;
}
cout<<"當前位於第"<<i<<"層,我們選擇電路板"<<x[j]<<", 通過計算得到跨越相鄰插槽"<<i<<"和"<<i+1<<"的導線數為:"<<ld<<", 目前得到的最大數值為:"<<cd<<endl;
cout<<"當前構造出的now[]數組為(now[j]表示當前解所含連接塊j的電路板數): ";
for(int j=1; j<=m; j++) cout<<now[j]<<" ";
cout<<endl;
if(cd > ld) //更新ld,cd為原來的最大密度,ld為當前的最大密度,哪個大取哪個 為什么要這么做?因為每一層我們只可以算出跨越插槽i和i+1的導線數,所以我們必須要和之前的最大值進行比較,取較大者(這是密度的定義)
{
ld = cd;
cout<<"ld<cd, ld已經被更新為"<<cd<<endl;
}
if(ld < bestd) //滿足剪枝函數,搜索子樹
{
swap(x[i], x[j]);
cout<<"滿足剪枝函數,遞歸深入一層,將到達第"<<i+1<<"層"<<endl;
Backtrack(i+1, ld);
cout<<"當前第"<<i+1<<"層,遞歸回退一層,將到達第"<<i<<"層"<<endl;
swap(x[i], x[j]);
for(int k=1; k<=m; k++) //恢復狀態
now[k] -= B[x[j]][k];
cout<<"第"<<i<<"層撤銷選擇電路板"<<x[j]<<",恢復now[]數組為(now[j]表示當前解所含連接塊j的電路板數): ";
for(int j=1; j<=m; j++) cout<<now[j]<<" ";
cout<<endl;
}
else cout<<"目前獲得的密度已經大於最優值,故直接剪枝."<<endl;
if(j==n) cout<<"當前層所有情況遍歷完,回溯"<<endl;
}
}
}
int Arrangement(int **B, int n, int m, int *bestx)
{
Board X;
//初始化X
X.x = new int[n+1];
X.total = new int[m+1];
X.now = new int[m+1];
X.B = B;
X.n = n;
X.m = m;
X.bestx = bestx;
X.bestd = m+1;
//初始化total和now
for(int i=1; i<=m; i++)
{
X.total[i] = 0;
X.now[i] = 0;
}
//初始化x為單位排列並計算total
for(int i=1; i<=n; i++)
{
X.x[i] = i;
for(int j=1; j<=m; j++)
X.total[j] += B[i][j];
}
cout<<"total數組為: ";
for(int i=1; i<=m; i++) cout<<X.total[i]<<" ";
cout<<endl;
X.Backtrack(1, 0);
delete[] X.x;
delete[] X.total;
delete[] X.now;
return X.bestd;
}
int main()
{
cout<<"請輸入電路板個數和連接塊個數:";
int n, m;
while(cin>>n>>m && n && m)
{
cout<<"輸入連接塊矩陣"<<endl;
int **B = new int*[n+1];
for(int i=0; i<=n; i++) B[i] = new int[m+1];
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin>>B[i][j];
int *bestx = new int[n+1];
for(int i=1; i<=n; i++) bestx[i] = 0;
int ans = Arrangement(B, n, m, bestx);
cout<<"得到的最小密度為:"<<ans<<endl;
for(int i=0; i<=n; i++) delete[] B[i];
delete[] B;
delete[] bestx;
cout<<"請輸入電路板個數和連接塊個數";
}
system("pause");
return 0;
}
//使用的B數組
//0 0 1 0 0
//0 1 0 0 0
//0 1 1 1 0
//1 0 0 0 0
//1 0 0 0 0
//1 0 0 1 0
//0 0 0 0 1
//0 0 0 0 1
針對第一個圖演示所得的結果:

運行結果:



我想清楚這題,人沒了家人們,/(ㄒoㄒ)/~~
上課沒聽懂,放了好久才回來寫題解.排列樹我就不畫了,畫出來人要沒了.
這題也可以將cd作為類的數據成員,從而減少Backtrack函數的傳入參數,不過對效率並沒有什么優化.
大伙應該能夠看懂吧...
歡迎大家訪問我的個人博客 --- 喬治的編程小屋,和我一起努力進步吧!