如果你对其他算法或者案例感兴趣,请考虑阅读我的以下文章。
递归案例-汉诺塔.
递归案例-正整数划分.
递归案例-全排列.
动态规划案例-矩阵连乘(含表格填写、问题理解、实例解决).
动态规划案例-最长公共子序列(含表格填写、问题理解、实例解决、例题答案).
问题
在一块电路板的上、下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]={01j<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;
}
}
输出结果
后话
- 首先给大家说一下,博主经常在线,如果有什么问题或者想法,可以在下方评论,我会积极反馈的。
- 其次还是要请大家能够多多指出问题,我也会在评论区等候大家!
.