1. 程式人生 > 實用技巧 >回溯法之電路板排列問題

回溯法之電路板排列問題

問題描述

將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},L={N1,N2,N3,N4,N5},N1={4, 5, 6},N2={2, 3},N3={1, 3},N4={3, 6},N5={7, 8};其中可能的排列如圖所示,則該電路板排列的密度是2。

上圖中,跨越插槽2和3,4和5,以及插槽5和6的連線數均為2。插槽6和7之間無跨越連線。其餘插槽之間只有1條跨越連線。在設計機箱時,插槽一側的佈線間隙由電路板的排列的密度確定因此,電路板排列問題要求對於給定的電路板連線條件(連線塊),確定電路板的最佳排列,使其具有最小密度。

問題分析

電路板排列問題是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間的連線密度。

演算法具體實現如下:

//電路板排列問題 回溯法求解
#include "stdafx.h"
#include <iostream>
#include <fstream> 
using namespace std;
 
ifstream fin("5d11.txt"); 
 
class Board
{
    friend int Arrangement(int **B, int n, int m, int bestx[]);
    private:
        void Backtrack(int i,int cd);
        
int n, //電路板數 m, //連線板數 *x, //當前解 *bestx,//當前最優解 bestd, //當前最優密度 *total, //total[j]=連線塊j的電路板數 *now, //now[j]=當前解中所含連線塊j的電路板數 **B; //連線塊陣列 }; template <class Type> inline void Swap(Type &a, Type &b); int Arrangement(int **B, int n, int m, int bestx[]); int main() { int m = 5,n = 8; int bestx[9]; //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} cout<<"m="<<m<<",n="<<n<<endl; cout<<"N1={4,5,6},N2={2,3},N3={1,3},N4={3,6},N5={7,8}"<<endl; cout<<"二維陣列B如下:"<<endl; //構造B int **B = new int*[n+1]; for(int i=1; i<=n; i++) { B[i] = new int[m+1]; } for(int i=1; i<=n; i++) { for(int j=1; j<=m ;j++) { fin>>B[i][j]; cout<<B[i][j]<<" "; } cout<<endl; } cout<<"當前最優密度為:"<<Arrangement(B,n,m,bestx)<<endl; cout<<"最優排列為:"<<endl; for(int i=1; i<=n; i++) { cout<<bestx[i]<<" "; } cout<<endl; for(int i=1; i<=n; i++) { delete[] B[i]; } delete[] B; return 0; } void Board::Backtrack(int i,int cd)//回溯法搜尋排列樹 { if(i == n) { for(int j=1; j<=n; j++) { bestx[j] = x[j]; } bestd = cd; } else { for(int j=i; j<=n; j++) { //選擇x[j]為下一塊電路板 int ld = 0; for(int k=1; k<=m; k++) { now[k] += B[x[j]][k]; if(now[k]>0 && total[k]!=now[k]) { ld ++; } } //更新ld if(cd>ld) { ld = cd; } if(ld<bestd)//搜尋子樹 { Swap(x[i],x[j]); Backtrack(i+1,ld); Swap(x[i],x[j]); //恢復狀態 for(int k=1; k<=m; k++) { now[k] -= B[x[j]][k]; } } } } } 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]; } } //回溯搜尋 X.Backtrack(1,0); delete []X.x; delete []X.total; delete []X.now; return X.bestd; } template <class Type> inline void Swap(Type &a, Type &b) { Type temp=a; a=b; b=temp; }
View Code

實現結果:

演算法效率:

在解空間排列樹的每個節點處,演算法Backtrack花費O(m)計算時間為每個兒子節點計算密度。因此計算密度所消耗的總計算時間為O(mn!)。另外,生成排列樹需要O(n!)時間。每次更新當前最優解至少使bestd減少1,而演算法執行結束時bestd>=0。因此最優解被更新的額次數為O(m)。更新最優解需要O(mn)時間。綜上,解電路板排列問題的回溯演算法Backtrack所需要的計算時間為O(mn!)。

轉載:https://blog.csdn.net/liufeng_king/article/details/8898372