软件构造Lab2中的P2要求我们使用P1已经完成的图数据结构重写Lab1中的FriendshipGraph类。在FriendshipGraph中,向图中加入边,加入点的操作重写很简单,直接调用即可。这一部分的核心应该在于求图中任意两点的最短距离,即getDistance方法的重写实现。
我在求解这一最短距离时使用了广度优先搜索算法,我的算法在实现上可能有所不同,代码上可能有些繁琐,但我认为是比较好理解的。
直接给出我写的代码:
public int getDistance(Person sourceed, Person targeted)
{
Person target, tempP;
Person FG = new Person("block");
target = sourceed;
int i = 0, sizeL, distance = 1, edgei;
Set<Person> targetSet = new HashSet<>();
Queue<Person> Q = new LinkedBlockingQueue<Person>();
Iterator<Person> targetIt = targetSet.iterator();
if(sourceed == targeted)
{
return 0;
}
Q.add(target);
Q.add(FG);
target.setFlag(1);
while(!Q.isEmpty())
{
target = Q.poll();
if(target == FG)
{
distance++;
if(!Q.isEmpty())
{
target = Q.poll();
}
else
{
return -1;
}
Q.add(FG);
}
if(graph.targets(target).containsKey(targeted))
{
return distance;
}
else
{
targetSet = graph.targets(target).keySet();
targetIt = targetSet.iterator();
while(targetIt.hasNext())
{
tempP = targetIt.next();
if(tempP.getFlag() == 0)
{
tempP.setFlag(1);
Q.add(tempP);
}
}
}
}
return -1;
}
我的代码实现思路如下:对于Person类,我在里面设置了一个标记量flag,并提供了get,set方法。这一变量用于标记这一点在广度优先搜索时是否被遍历到,以保证每一次遍历时不会遍历到前面遍历过的点。同时在代码开端,我设置了一个分隔对象,Person FG,这一对象的名字被命名为block,用于分割不同层次。如果我们这么设计,需要在代码开端的规约中强调图中有实际意义的点不应被命名为"block"。
代码的具体实现依赖于一个队列。首先判断输入的起始点与目标点时同一点的话则返回0,如果不是同一点,将起始点入队列,并将这一点flag设置为1,同时入队列一个分隔符FG(第一层只有起始点一个点)。随后进入一个while循环,如果队列不空,则执行循环。循环中先从队列弹出一点作为这一次循环的比较点,如果弹出的为分隔符,那么说明其所对的这一层点已经都遍历完成,我们将距离加一,并再弹出一点作为这一次循环的比较点,同时再入一个分隔符。使用图中已经实现的targets方法返回一个Map,利用Map的containsKey方法可以简单判断这一点的所有儿子是否存在目标点,如果存在,搜索完成,返回distance值。如果不存在,利用KeySet方法将包含其所有儿子的set取出,设置一个迭代器遍历,把遍历到的每一个点入队列并设置标记flag为1 。
一些可能会有的疑问:
为什么不在每一次循环结束时加一个分隔符?
分隔符的作用本质上是为了计算diatance。distance值是顺着层次递增的,每多一层则distance应加一,否则distance应不变。所以分隔符应该分隔的是不同层次。而同一层次可能包含两个或两个以上的点,例如:A->B,A->C,那么B,C再同一层次,A到B,A到C的距离应该都为1。如果每一次循环结束都加入一个分隔符,则B,C之间会加入一个分隔符。在代码上认为B,C属于两个层次。A到B,C的距离会变成一个2,一个3。具体哪个是2取决于迭代器先遍历到哪个。
为什么要在每一次弹出分隔符时再入一个分隔符?
每一次弹出分隔符证明这一分隔符所对应的层所有点已经搜索完毕,即这一层所对应的所有点的儿子恰好都已经被遍历并入队列。可以理解的是,这一层所有点的所有儿子组成下一层的所有点,也就是此时下一层的所有点恰好都已经入队列,此时再入队列应该不再属于这一层了,所以应该入一个分隔符。
值得注意的是,弹出分隔符时,再弹出下一点之前应该判断队列是否为空,因为最后一层后面也会跟一个分隔符。