無向圖遍歷算法
最近遇到一個需要遍歷無向圖的算法,需要從圖的某一個結點開始遍歷,找出到某一類結點結束的所有路徑。網上找了很多算法,都不是很靠譜。後來發現這個比較靠譜。整理如下:
算法目標:
在一個無向連通圖中求出兩個給定點之間的所有路徑;
算法思路:
-
整理節點間的關係,爲每個節點建立一個集合,該集合中保存所有與該節點直接相連的節點(不包括該節點自身);
-
定義兩點一個爲起始節點,另一個爲終點,求解兩者之間的所有路徑的問題可以被分解爲如下所述的子問題:對每一 個與起始節點直接相連的節點,求解它到終點的所有路徑(路徑上不包括起始節點)得到一個路徑集合,將這些路徑集合相加就可以得到起始節點到終點的所有路徑;依次類推就可以應用遞歸的思想,層層遞歸直到終點,若發現希望得到的一條路徑,則轉儲並打印輸出;若發現環路,或發現死路,則停止尋路並返回;
-
用棧保存當前已經尋到的路徑(不是完整路徑)上的節點,在每一次尋到完整路徑時彈出棧頂節點;而在遇到從棧頂節點無法繼續向下尋路時也彈出該棧頂節點,從而實現回溯。
代碼及測試如下:
import java.util.*;
public class test {
/* 臨時保存路徑節點的棧 */
public static Stack<Node> stack = new Stack<Node>();
/* 存儲路徑的集合 */
public static ArrayList<Node[]> sers = new ArrayList<>();
/* 判斷節點是否在棧中 */
public static boolean isNodeInStack(Node node) {
Iterator<Node> it = stack.iterator();
while (it.hasNext()) {
Node node1 = it.next();
if (node == node1)
return true;
}
return false;
}
/* 此時棧中的節點組成一條所求路徑,轉儲並打印輸出 */
public static void showAndSavePath() {
Node[] o = stack.stream().toArray(Node[]::new);
for (int i = 0; i < o.length; i++) {
Node nNode = o[i];
if (i < (o.length - 1))
System.out.print(nNode.getName() + "->");
else
System.out.print(nNode.getName());
}
sers.add(o); /* 轉儲 */
System.out.println("\n");
}
/*
* 尋找路徑的方法
* cNode: 當前的起始節點currentNode
* pNode: 當前起始節點的上一節點previousNode
* sNode: 最初的起始節點startNode
* eNode: 終點endNode
*/
public static boolean getPaths(Node cNode, Node pNode, Node sNode) {
Node nNode = null;
/* 如果符合條件判斷說明出現環路,不能再順着該路徑繼續尋路,返回false */
if (cNode != null && pNode != null && cNode == pNode)
return false;
if (cNode != null) {
int i = 0;
/* 起始節點入棧 */
stack.push(cNode);
/* 如果該起始節點就是終點,說明找到一條路徑 */
//這裏可以增加多個規則,比如不經過某些結點,某類結點標識出口,如果存在重複結點及路徑的時候
//可以通過記錄已有路徑的首位結點來過濾重複路徑等。
if (cNode.name.equalsIgnoreCase("node2")) return true;
if (cNode.name.equalsIgnoreCase("node8")) {
/* 轉儲並打印輸出該路徑,返回true */
showAndSavePath();
return true;
}
/* 如果不是,繼續尋路 */
else {
/*
* 從與當前起始節點cNode有連接關係的節點集中按順序遍歷得到一個節點
* 作爲下一次遞歸尋路時的起始節點
*/
nNode = cNode.getRelationNodes().get(i);
while (nNode != null) {
/*
* 如果nNode是最初的起始節點或者nNode就是cNode的上一節點或者nNode已經在棧中 ,
* 說明產生環路 ,應重新在與當前起始節點有連接關係的節點集中尋找nNode
*/
if (pNode != null && (nNode == sNode || nNode == pNode || isNodeInStack(nNode))) {
i++;
if (i >= cNode.getRelationNodes().size())
nNode = null;
else {
nNode = cNode.getRelationNodes().get(i);
}
continue;
}
/* 以nNode爲新的起始節點,當前起始節點cNode爲上一節點,遞歸調用尋路方法 */
if (getPaths(nNode, cNode, sNode))/* 遞歸調用 */ {
/* 如果找到一條路徑,則彈出棧頂節點 */
stack.pop();
}
/* 繼續在與cNode有連接關係的節點集中測試nNode */
i++;
if (i >= cNode.getRelationNodes().size())
nNode = null;
else
nNode = cNode.getRelationNodes().get(i);
}
/*
* 當遍歷完所有與cNode有連接關係的節點後,
* 說明在以cNode爲起始節點到終點的路徑已經全部找到
*/
stack.pop();
return false;
}
} else
return false;
}
public static void main(String[] args) {
/* 定義節點數組 */
Node[] node = new Node[11];
for (int i = 0; i < node.length; i++) {
node[i] = new Node();
node[i].setName("node" + (i + 1));
node[i].id = i;
}
node[0].setRelationNodes(Arrays.asList(node[3], node[1], node[10]));
node[1].setRelationNodes(Arrays.asList(node[0], node[2], node[3]));
node[2].setRelationNodes(Arrays.asList(node[1], node[4], node[10]));
node[3].setRelationNodes(Arrays.asList(node[0], node[1], node[5], node[8], node[10]));
node[4].setRelationNodes(Arrays.asList(node[2], node[8], node[9]));
node[5].setRelationNodes(Arrays.asList(node[3], node[6]));
node[6].setRelationNodes(Arrays.asList(node[5], node[7], node[8]));
node[7].setRelationNodes(Arrays.asList(node[6], node[8]));
node[8].setRelationNodes(Arrays.asList(node[1], node[3], node[6], node[7], node[4], node[10]));
node[9].setRelationNodes(Arrays.asList(node[4], node[10]));
node[10].setRelationNodes(Arrays.asList(node[9]));
/* 開始搜索所有路徑 */
getPaths(node[0], null, node[0]);
}
public static class Node {
private Integer id;
public String name = null;
public List<Node> relationNodes = new ArrayList<Node>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Node> getRelationNodes() {
return relationNodes;
}
public void setRelationNodes(List<Node> relationNodes) {
this.relationNodes = relationNodes;
}
}
}
以上算法參考無向圖如下:
[圖片]
計算的是從1開始,尋找出口爲8的路徑,路徑中不包含2結點。
結果如下
node1->node4->node6->node7->node8
node1->node4->node6->node7->node9->node8
node1->node4->node9->node7->node8
node1->node4->node9->node8
node1->node4->node11->node10->node5->node9->node7->node8
node1->node4->node11->node10->node5->node9->node8
node1->node11->node10->node5->node9->node4->node6->node7->node8
node1->node11->node10->node5->node9->node7->node8
node1->node11->node10->node5->node9->node8