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

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

 

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

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

动态规划案例-电路布线
问题
问题分析
 理解题意
 为什么要用动态规划
 如何用动态规划解决这道题
  规定
  推论
  实例解决
Java代码
输出结果
后话
问题
在一块电路板的上、下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时:
s i z e [ i , j ] = { 0 j < n ( i ) 1 j > = n ( i ) } size [i,j]= \begin {Bmatrix} 0&&&&&j<n(i)\\ 1&&&&&j>=n(i) \end{Bmatrix}
size[i,j]={
0
1










j<n(i)
j>=n(i)

}

   当i=>1时:
s i z e [ i , j ] = { s i z e [ i − 1 , j ] j < n ( i ) m a x ( s i z e [ i − 1 , j ] , s i z e [ i − 1 , 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}
size[i,j]={
size[i−1,j]
max(size[i−1,j],size[i−1,n(i)−1]+1)










j<n(i)
j>=n(i)

}

.   我们来分析上述式子是如何得来的:
   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 1 1 1 1 2 2 2 2 2 2
7 0 1 1 1 1 2 2 2 2 3 3
8 0 1 1 2 2 2 2 2 2 3 3
9 0 1 1 2 2 2 2 2 2 3 4
10 0 1 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;
}

解释:

从最后一个开始,判断是否等于上一个
等于的话,就继续往上找
不等于的话记录这个边,并且将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;
}
}



输出结果


后话
首先给大家说一下,博主经常在线,如果有什么问题或者想法,可以在下方评论,我会积极反馈的。
其次还是要请大家能够多多指出问题,我也会在评论区等候大家!
.
————————————————
版权声明:本文为CSDN博主「小王在努力」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/vangoudan/article/details/106413539

posted @ 2023-10-11 19:33  菜鸡一枚  阅读(187)  评论(0)    收藏  举报