动态规划案例-电路布线(含表格填写等超详细,纯人话讲解)

如果你对其他算法或者案例感兴趣,请考虑阅读我的以下文章。

递归案例-汉诺塔.
递归案例-正整数划分.
递归案例-全排列.
动态规划案例-矩阵连乘(含表格填写、问题理解、实例解决).
动态规划案例-最长公共子序列(含表格填写、问题理解、实例解决、例题答案).

问题

在一块电路板的上、下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. 其次还是要请大家能够多多指出问题,我也会在评论区等候大家!
    在这里插入图片描述 .
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章