昨天有个朋友找我帮个忙,需要我帮他看个代码,实现从无向图中输出两个节点中的路径。
他给了我一份代码,但是在遍历的过程中测试了很多次,基本都没有输出。我看了一下代码是深度优先遍历的,但是由于他的无向图节点有一万多个,并且每个节点的邻接节点又有几百几千个,在深度遍历过程中这个n叉树得不到控制,遍历路径的时间完全随缘,运行几小时也遍历不完。并且由于是深度遍历,输出的路径也是随缘,不会达到先输出最短路径,后输出最长路径,确实不满足朋友的需求。
为此,我先想到剪枝,虽然是深度遍历的,但是可以控制树的深度,我将树的深度控制到5,即如果达到第5层还没有路径输出,则抛弃当前节点,回溯至父节点。改完之后有所改善,但是改善并不大,还是有很多用例需要运行很长时间得不到输出。
因此,我想将其改为广度遍历,就可以按照路径的长短顺序进行输出。但是写程序的过程中,发现如果简单的用队列的话,只能判断两个节点能否存在路径,并不能向前推出完整的路径,因为前面的父节点已经从队列中删除。思考之后,我用了两个ArrayList,一个queueList加上一个游标来模拟队列,一个indexList存储当前节点的父节点在queueList中的位置,为输出路径做准备。
代码如下,由于时间仓促,没有好好整理,使用过程中有问题欢迎讨论,见谅。
此代码是从读文件然后构造无向图,可以忽视,使用的话可根据自己的情况构造一个Node数组就行了。
广度遍历主类代码:
package lijia;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
public class MyFindPathBFS {
public static void main(String[] args) {
// TODO 自动生成的方法存根
ArrayList<ArrayList<Integer>> myList = new ArrayList<ArrayList<Integer>>();
for(int i=0;i<=13971;i++){
myList.add(new ArrayList<Integer>());
}
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader("1.net");
br = new BufferedReader(fr);
String str = null;
while((str=br.readLine())!=null){
String[] nodes = str.split(" ");
int src = Integer.parseInt(nodes[0]);
int des = Integer.parseInt(nodes[1]);
if(!myList.get(src).contains(des)){
myList.get(src).add(des);
}
if(!myList.get(des).contains(src)){
myList.get(des).add(src);
}
}
} catch (FileNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
int length = 13972;
/* 定义节点关系 */
// int nodeRalation[][] = { { 1 }, // 0
// { 0, 5, 2, 3 },// 1
// { 1, 4 }, // 2
// { 1, 4 }, // 3
// { 2, 3, 5 }, // 4
// { 1, 4 } // 5
//
// };
/* 定义节点数组 */
Node[] node = new Node[length];
for (int i = 0; i < length; i++) {
node[i] = new Node();
node[i].setName(i);
}
/* 定义与节点相关联的节点集合 */
for (int i = 0; i < length; i++) {
ArrayList<Node> List = new ArrayList<Node>();
ArrayList<Integer> temp = myList.get(i);
int len = temp.size();
for (int j = 0; j < len; j++) {
List.add(node[temp.get(j)]);
}
node[i].setRelationNodes(List);
List = null; // 释放内存
}
/* 开始搜索所有路径 */
new MyFindPathBFS().getPaths(node[8073], node[12351], node);
}
private void getPaths(Node src,Node des,Node[] nodes) {
int num = 0;
// 模拟队列
ArrayList<Node> queueList = new ArrayList<Node>();
// 存储下标
ArrayList<Integer> indexList = new ArrayList<Integer>();
// 源节点入队
queueList.add(src);
// 设置源节点的父节点的下标为-1,做标志作用
indexList.add(-1);
int index = 0;
// 存储连边,不能来回重复放置连边
HashSet<String> set = new HashSet<String>();
while(index<queueList.size()){
// index即为目前队列的头结点
Node top = queueList.get(index);
// 如果是目的元素,则打印路径,目前设置了输出前三条路径
if(top.name==des.name){
printPath(queueList,indexList,index);
num = num + 1;
if(num==3)
System.exit(1);
}
// 得到当前节点的邻接节点,并入队,并存储入队新节点的父节点的下标
ArrayList<Node> relation = top.relationNodes;
String edgeTopNode = null;
String edgeNodeTop = null;
for(Node node:relation){
edgeTopNode = top.name + "," + node.name;
edgeNodeTop = node.name + "," + top.name;
if(!set.contains(edgeTopNode)&&!set.contains(edgeNodeTop)){
queueList.add(node);
indexList.add(index);
set.add(edgeTopNode);
}
}
// System.out.print("queueList:");
// for(Node node:queueList){
// System.out.print(node.name+" ");
// }
// System.out.println();
// 游标向前加1,即下一个要出队的元素,模拟队列
index = index + 1;
}
}
private void printPath(ArrayList<Node> queueList,ArrayList<Integer> indexList, int index) {
// 由index节点向前推
System.out.print(queueList.get(index).name+"<-");
index = indexList.get(index);
if(index==-1){
System.out.println(queueList.get(0).name);
return;
}
while(index!=0){
System.out.print(queueList.get(index).name+"<-");
index = indexList.get(index);
}
System.out.println(queueList.get(0).name);
return;
}
}
节点数据结构:
package lijia;
import java.util.ArrayList;
/* 表示一个节点以及和这个节点相连的所有节点 */
public class Node {
public int name = 0;
public ArrayList<Node> relationNodes = new ArrayList<Node>();
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public ArrayList<Node> getRelationNodes() {
return relationNodes;
}
public void setRelationNodes(ArrayList<Node> relationNodes) {
this.relationNodes = relationNodes;
}
}
输出如下,路径还是从后往前,可在打印路径函数中加入辅助Stack等数据结构,即可实现从前往后打印。
12351<-6224<-7762<-8073
12351<-6224<-9697<-8073
12351<-10757<-9419<-8555<-9376<-8073