用Java集合类实现图的广度优先算法
第一次Java实验,就碰到了要求实现无向图两点间距离计算的题目。被迫拾起了数据结构的知识。
刚刚着手学习Java编程语言,加上之前数据结构的知识也都还给老师,就算还记得,也不知道怎样用Java实现图的结构与算法啊QAQ
没办法,最后硬着头皮,用Java集合类勉强写出来了。
过程可能要麻烦很多,我相信肯定也被很多人写过,不过我实在太懒了,也没有搜过相关文章。如果实现过程中有问题,还请大佬指出。
先给出题目条件吧。这里要存储的点是一个自定义的Person类,当然,你也可以使用任何的其他对象。
Map<Person,Set<Person>> graph = new HashMap<Person,Set<Person>>();
我用一个HashMap对象graph来存储我的图。这里,Person是图中的每一个顶点,Set<Person>中存放的是每一个和Person相邻接的顶点。
添加顶点与添加边的方法比较简单,不具体写出来了,简述一下吧。
boolean addVertex(Person name)
void addEdge(Person name1,Person name2)
函数addVertex(Person name)将Person类name加入graph中,并为name新建一个集合,加入name在graph中映射到的值里。
函数addEdge(Person name1, Person name2)则分别将name1加入name2映射到的集合graph.get(name2)中,将name2加入到name1映射到的集合graph.get(name1)中。
接下来,就是用集合实现广度优先算法来获取距离了。话不多说,先给出代码。
int getDistance(Person name1,Person name2)
{
int distance = 0;
if(graph.containsKey(name1)&&graph.containsKey(name2)) //有这两个人
{
Map<Person,Integer> search = new HashMap<Person,Integer>(); //search标记邻接点是否被访问过
Set<Person> set = new HashSet<Person>(); //已经遍历到的全部点的集合set
set.add(name1);
search.put(name1,0);
while(search.containsValue(0)) //存在没有遍历的点
{
if(set.contains(name2)) //已经搜索到了name2
{
return distance;
}
Set<Person> friend = new HashSet<Person>(); //新一轮遍历到的全部点的集合
Iterator<Person> it = set.iterator(); //遍历全部点的集合
while(it.hasNext())
{
Person x = it.next(); //从set中依次取出
if(search.get(x) == 0)
{
friend.addAll(graph.get(x)); //将x的邻接的点全部添加到集合friend中
search.put(x, 1); //标记x的邻接点已访问
}
}
set.addAll(friend); //将新集合并入原集合set
it = set.iterator();
while(it.hasNext()) //遍历集合set
{
Person x = it.next();
if(search.containsKey(x) == false) //存在search中未加入的新点
{
search.put(x, 0);
}
}
distance = distance + 1; //完成一次搜索,距离加1
}
return -1;
}
else
{
System.out.println("error!根本没这人");
System.exit(0);
}
return distance;
}
首先,我们要保证这两个点在图中确实存在,然后我们再进行下一步。
用distance标记两人之间的距离,这里用了另一个HashMap对象search标记一个顶点是否被访问过邻接点。
Map<Person,Integer> search = new HashMap<Person,Integer>();
用HashSet存储name1可达的顶点(我们从name1出发,搜索name2)。
Set<Person> set = new HashSet<Person>();
如果一个顶点Person的邻接点被存入了set中,我们就把Person映射到的值标记为1,反之,如果点Person的邻接点还没有被存入set,我们将Person映射到的值标记为0。
下面我们正式开始。
首先我们将name1加入set,并将映射(set,0)写入哈希表search,因为此时我们仅仅是将name1写入了set,并没有将set的邻接点写入set。
接下来就要进入循环了,循环条件为search.containsValue(0),也就是说在set中,存在着一些点,它们的邻接点还没有被添加进set中,此时我们可以继续添加这些点。
循环开始,我们先判断了name2是否存在于set中(不排除name2等于name1的可能),如果name2已存在于set中,返回distance(name2等于name1时,distance为0)。
接下来,我再次new了一个HashSet的集合。
Set<Person> friend = new HashSet<Person>();
friend存储的是这一轮循环过程中,将要被添加到set中的点,因为set需要遍历,担心直接添加进set中会引起混乱(这个我并没有进行实验,感兴趣的可以尝试一下),但是用个friend缓冲一下,就不会遇到这种问题了。
接下来,开始遍历set,依次从set中取出元素,如果这个元素被search标记为0,即它的邻接点并未被添加(我们暂时不考虑邻接点有哪些,有可能其实它的邻接点已经全部被添加进set中了,但是我们是不知道的,我们必须通过再添加一次,来保证不产生遗漏,Set类就像数学中的集合一样,会帮我们去掉重复的元素),我们将它映射到的集合添加到friend中(即将所有邻接点加入friend中)并将这个元素重新标记为1。
全部遍历完成后,我们再将集合friend并入到集合set中。
紧接着,我们再一次遍历set集合,如果set中存在元素,在search中不存在(即前一轮遍历得到的friend中存在着新的,原set中不存在的元素,被加入到了set中),则将此元素添加到search中,并使其映射到0。
遍历完成后,distance加1,即在原先已遍历到的顶点的前提上再次前进了一节。
接下来,继续进行最外层的循环。如果set中存在被标记为0的元素,则继续进行前面的步骤。
如果不存在被标记为0的元素,则说明上一轮的搜索并没有增加新的元素,我们已经完全搜索了name1可达的节点,这应该是图的一个连通子图。如果name2存在于这个子图中,由于新一轮搜索并没有增加新的标记为0的节点,也就是说在之前一轮的循环结束,set中就已经包含了这个连通子图,而在上一轮的循环中,显然如果存在name2,那么在if条件的判断时就应该已经返回了结果。因此,name2是不存在这个连通子图中的,也就是说name1无法到达name2,于是,我们返回一个-1。
有点啰嗦,写的有点多,希望大佬们不要见怪。