動態規劃案例-電路佈線(含表格填寫等超詳細,純人話講解)

如果你對其他算法或者案例感興趣,請考慮閱讀我的以下文章。

遞歸案例-漢諾塔.
遞歸案例-正整數劃分.
遞歸案例-全排列.
動態規劃案例-矩陣連乘(含表格填寫、問題理解、實例解決).
動態規劃案例-最長公共子序列(含表格填寫、問題理解、實例解決、例題答案).

問題

在一塊電路板的上、下2端分別有n個接線柱。根據電路設計,要求用導線(i,π(i))將上端接線柱與下端接線柱相連,確定將哪些連線安排在第一層上,使得該層上有儘可能多的連線。該問題要求確定導線集Nets={(i,π(i)),1≤i≤n}的最大不相交子集。
在這裏插入圖片描述

問題分析

 理解題意

  相信有很多人和我開始做這道題有一樣的思想:“這鬼問題到底在問啥?啥是最大不相交子集?”
  現在由我來給大家講一下這個題到底要幹嘛。
  在製作電路板的時候,要保證電線不能相交,但是我們看問題給的圖中的電線有很多相交的,那麼怎麼辦?我們可以分層!把不相交的放在一層。我們可以知道,如果第一層分的不相交的電線很多的話,我們就可以少分幾層,從而節省成本了,這也就是這道題所求的,第一層最多可以放多少根不相交的電線。

 爲什麼要用動態規劃

  1.首先,這個問題符合最優子結構性質:問題的最優解包含子問題的最優解。舉個簡單的例子來理解這句話:一個國家裏面最厲害的兵,肯定是他所在的軍營裏面最厲害的兵;各個軍營裏面最厲害的兵,經過選拔(武舉考試?)就可以有人脫穎而出,成爲這個國家最厲害的兵。
  2.其次就是,重疊子問題性質:子問題之間不獨立的同時(這是區分分治算法的關鍵),少量子問題被重複解決了。說白了就是子問題之間有聯繫的同時有些計算重複了,我們可以在計算第一次的時候將結果記錄下來,以後再用到的時候,直接取值,不用再去花時間計算了。

 如何用動態規劃解決這道題

  規定

   1.我們規定題目圖中 與i連線的對應點 爲n(i),比如n(1)=8。
   2.我們規定N(i,j)爲i-j連線的邊,當 j 等於 n(i) 時,我們稱N(i,j)爲有效邊,否則爲無效邊。例如,N(1,8)爲有效邊,N(1,3)爲無效邊。
   3.我們規定size(i,j)爲存儲到第I條邊的時候,第一層內不相交的有效邊的最多個數。

  推論

   通過上述規定,結合題意,我們可以得到下列推論:

   當i=1時:
size[i,j]={0j<n(i)1j>=n(i)}size [i,j]= \begin {Bmatrix} 0&&&&&j<n(i)\\ 1&&&&&j>=n(i) \end{Bmatrix}

   當i=>1時:
size[i,j]={size[i1,j]j<n(i)max(size[i1,j]size[i1,n(i)1]+1)j>=n(i)}size [i,j]= \begin {Bmatrix} size[i-1,j]&&&&&j<n(i)\\ max(size[i-1,j],size[i-1,n(i)-1]+1)&&&&&j>=n(i) \end{Bmatrix}
.   我們來分析上述式子是如何得來的:
   1.當i=1時,j<n(i)。代表的是與第一個點相連的前無效邊,那麼他們的size就是0,因爲size是:第一層內不相交的有效邊的最多個數
   2.當i=1時,j>=n(i)。當j=n(i)的時候,代表N(i,j)就是有效邊了,此時是第一條有效邊,不用顧慮其他,將這條邊加入到第一層中,也就是size=1;當j>n(i)的時候,代表N(i,j)爲後無效邊,他的影響不了size的大小,所以size還是1。
   3.當i>1時,j<n(i)的時候,代表着是與第i個點相連的前無效邊,此時的N(i,j)的size依賴於N(i-1,j)的size,我們用反證法來證明:
   如果size(i,j)!=size(i-1,j),那麼就有size(i,j)>size(i-1,j)或者size(i,j)<size(i-1,j)
   size(i,j)>size(i-1,j),當我們要讓size(i,j)>size(i-1,j)的時候,代表着我們要有一條新的有效邊加入,但是題目中j<n(i),此時的邊都爲無效邊,沒有有效邊,與條件不符。
   size(i,j)<size(i-1,j),這個顯然不可能,沒加邊就不錯了,還能減邊?
   4.當i>1時,j>=n(i)的時候。當j=n(i)的時候,也就是N(i,j)爲有效邊時,我們考慮兩方面:1.這個有效邊可以放進第一層2.這個有效邊不可以放進第一層。
   1.這個有效邊可以放進第一層的話,那麼此時的size=size[i-1,n(i)-1]+1)了。此處一位博主解釋的十分清楚,我在此借用他的圖片來闡述一下。(這位博主的博客風仲達
在這裏插入圖片描述
   2.不可以可以放進第一層的話,那麼此時的size=size[i-1,j]了。
在這裏插入圖片描述

  實例解決

   我們一起來解決這道題:
在這裏插入圖片描述
   我們採用填表格的方式,填表格的規則我們可以從上面的推論中總結出來:
   1.當i=1時,j<n(i),填0,j>=n(i),填1。
   2.當i>1時,j<n(i),填他上方格子上面的數。
   3.當i>1時,j>=n(i),比較 他上方格子 和 第(i-1,n(i)-1)個格子的結果+1 的大小,取較大者填。

這是分界線

j \ 1 2 3 4 5 6 7 8 9 10
\ 0 0 0 0 0 0 0 0 0 0
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 0
10 0

這是分界線
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
由於篇幅原因,我就直接上結果:

i\j \ 1 2 3 4 5 6 7 8 9 10
\ 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 1 1 1
2 0 0 0 0 0 0 0 1 1 1 1
3 0 0 0 0 1 1 1 1 1 1 1
4 0 0 1 1 1 1 1 1 1 1 1
5 0 0 1 1 1 2 2 2 2 2 2
6 0 0 1 1 1 2 2 2 2 2 2
7 0 0 1 1 1 2 2 2 2 3 3
8 0 0 1 2 2 2 2 2 2 3 3
9 0 0 1 2 2 2 2 2 2 3 4
10 0 0 1 2 2 2 3 3 3 3 4

根據這個表找結果的方法是:

for(int i=n;i>1;i--)
   if(size[i][j]!=size[i-1][j])
   {
    net[m++]=i;
    j=c[i]-1;
   }

解釋:

  1. 從最後一個開始,判斷是否等於上一個
  2. 等於的話,就繼續往上找
  3. 不等於的話記錄這個邊,並且將j值賦爲c[i]-1

在這裏插入圖片描述
因此,此題的答案爲:(3-4),(5-5),(7-9),(9-10)

Java代碼

此代碼爲我們實驗用的參考代碼:


public class Test12_3 {
	public static void main(String []args){
	  WireSet ws= new WireSet(10);
	  //計算最優值
 	 ws.mnset(ws.c, ws.size);
  	//構造最優解
 	 ws.m=ws.traceback(ws.c, ws.size, ws.net);
 	 //輸出結果
	  ws.print();
	 }
}
class WireSet {
	 public int n;  //導線的數目
	 public int m;  
	 public int []c; //存放導線
	 public int [][]size;
	 public int []net; //存放最大公共不相交子集
	 //構造函數:根據num的值所表示的導線的數目,進行初始化
	 public WireSet(int num)
	 {
	  	n=num;
	  	c=new int [n+1];
	  	size=new int [n+1][n+1];
		  net=new int [n+1];
	  	//對c[]進行賦初值,爲1-n的任一個排列
	  	c[1]=(int)(Math.random()*(n)+1);
	  	int i=2;
	  	while(i<=n)
	  	{
	  		int f=0;
	   		int t=(int)(Math.random()*(n)+1);
	   		for(int j=1;j<i;j++)
	   		{
	    			if (c[j]==t) 
	    			{
	     			f=1;
	     			break;
	    			}
	   		}
	   		if (f==0){
	    			c[i]=t;
	    			i++;
	   		}
	  	}
	 }
	 //用來輸出相關信息
	 public void print()
	 {
	  	for(int i = 1;i<=n;i++)
	  	{
	  	 	for(int j = 0;j<=n;j++)
	   	 	System.out.print(size[i][j]);
	  	 	System.out.println();
	  	}
	  	//輸出上端線路編號
	  	System.out.print("上端線路編號:");
	  	for(int i=0;i<=n;i++)
	  	{
	  		 System.out.print(String.format("%3d", i));
	  	}
	  	System.out.println();
	  	System.out.println();
	  	//輸出下端線路編號
	  	System.out.print("下端線路編號:");
	  	for(int i=0;i<=n;i++)
	  	{
	   		System.out.print(String.format("%3d", c[i]));
	  	}
	  	System.out.println();
	  	//輸出最大不相交子集的個數
	  	System.out.print("最大不相交子集的大小爲:"+size[n][n]);
	  	System.out.println();
	  	//輸出最大不相交子集中的各個導線
	  	System.out.print("上端線路編號:");
	  	for(int i=this.m-1;i>=0;i--)
	  	{
	   		System.out.print(String.format("%3d", this.net[i]));
	  	}
	  	System.out.println();
	  	System.out.print("下端線路編號:");
	  	for(int i=this.m-1;i>=0;i--)
	 	 {
	  	 	System.out.print(String.format("%3d", c[this.net[i]]));
	  	}
	  
	 }
	 
	 //[]c:導線上下兩端對應的關係:i=c[j],上端i導線對應下端j導線
	 //size[][]:用來記錄最大不相交子集的大小
	 public static void mnset(int []c,int [][]size)
	 {
	  	int n=c.length-1;
	  	//j<c[1],i=1,最大不相交子集爲空
	  	for(int j=0;j<c[1];j++)
	   	size[1][j]=0;
	  	//j≥c[1],i=1,最大不相交子集
	  	for(int j=c[1];j<=n;j++)
	   	size[1][j]=1;
	  	for(int i=2;i<n;i++)
	  	{
	   		for(int j=0;j<c[i];j++)
	   	 		size[i][j]=size[i-1][j];
	   		for(int j=c[i];j<=n;j++)
	    			size[i][j]=Math.max(size[i-1][j],size[i-1][c[i]-1]+1);
	  	}
	  	size[n][n]=Math.max(size[n-1][n],size[n-1][c[n]-1]+1);
	 }
	public static int traceback(int []c,int [][]size ,int []net)
	{
		int n=c.length-1;
		int j=n;
	  	int m=0;
	  	for(int i=n;i>1;i--)
	   		if(size[i][j]!=size[i-1][j])
	   		{
	    			net[m++]=i;
	    			j=c[i]-1;
	   		}	
	  	if(j>=c[1])
	   		net[m++]=1;
	  	return m;
	 }
}
	
	

輸出結果

在這裏插入圖片描述

後話

  1. 首先給大家說一下,博主經常在線,如果有什麼問題或者想法,可以在下方評論,我會積極反饋的。
  2. 其次還是要請大家能夠多多指出問題,我也會在評論區等候大家!
    在這裏插入圖片描述 .
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章