今天我準備用Java來求解圖論算法中的歐拉回路(EulerCircuit)問題,這個名字可能對大家有些陌生,如果我說一筆畫,大家肯定就都清楚了。原來有一個"七橋問題",不知道的可以百度,這裏就不詳述。繼而引出了一筆畫的問題。後來歐拉大神就提出了一筆畫的一個原則,那就是一個圖形如果可以一筆畫,那麼把它抽象爲圖的話,度爲奇數的頂點只能是0個或兩個。意思也就是,可以一筆畫的圖形,頂點要麼全部都是度爲偶數的,要麼有且僅有兩個度爲奇數的頂點,而且一筆畫路徑的開始和結束,都得是這兩個度爲奇數的頂點。然後一筆畫問題就又叫歐拉回路了
如何用Java編程,來實現圖論算法的歐拉回路問題,求解出圖的一筆畫路徑呢?今天我準備以大家都熟知的奧運五環爲例,來告訴大家我的實現思路和過程:
以上就是奧運五環,共有八個頂點,每個頂點的度都是偶數。根據歐拉定理,可以從任意頂點出發對奧運五環進行一筆畫。我們可以先把奧運五環的頂點進行標記,如下圖:
以下是我的實現思路:
- 檢查一筆畫圖形每個頂點的度,如果不是全偶數度頂點或有且僅有兩個奇數度頂點,則該圖形無法一筆畫
- 先將一筆畫圖形的所有頂點進行編號,如上圖
- 然後計算每個頂點之間的連線數,如奧運五環圖,A和B之間有3條連線,C和D之間有兩條連線,D和E之間有1條連線
- 將一筆畫圖形抽象爲新的圖,重新分佈頂點,將所有有連線的頂點都加上一條邊,邊是無向邊,且權重爲他們之間的連線數
- 從某一點出發(有且僅有兩個度爲奇數的頂點的圖,則必須以其中一個奇數度頂點出發),然後遞歸用深度優先搜索DFS來探索一筆畫路徑,每走過一條邊,則邊的權重減一。如果當前頂點和下一個頂點之間的權重減到了0,或者本來就是0,則兩點之間不可達,就換其他可達的頂點,繼續遞歸往深處遍歷,直到完成一筆畫,或者接下來所有相鄰頂點之間的邊權重都爲0,無路可走
- 如果無路可走但完成了一筆畫(所有邊的權重都爲0了),則可以輸出正確結果,完美結束遞歸。如果未完成一筆畫且無路可走,直接結束當前分支遞歸
- 初始化決定一個開始頂點,求出所有下一個可達的頂點集,遍歷下一個頂點集,多批次遞歸尋路,以求出圖形的所有一筆畫路徑
下面這張圖,就是上面步驟4中說的奧運五環抽象的圖,頂點間的邊的權重,就是在奧運五環中的連線數:
以下是上面的奧運五環抽象圖的鄰接矩陣:
下面是我用Java實現的歐拉回路的求解算法代碼,並以奧運五環爲例,用該算法求出 “奧運五環” 的所有一筆畫畫法路徑。畫的時候如果從A點到B點有多條路徑,隨意畫一條路徑即可,都是等價的。算法的原理和精髓都在下面的代碼和其間的註釋之中:
import java.util.ArrayList;
import java.util.List;
/**
* @author LiYang
* @ClassName EulerCircuit
* @Description 求解歐拉回路(一筆畫)問題
* @date 2019/11/20 16:05
*/
public class EulerCircuit {
/**
* 返回奧運五環圖的鄰接矩陣(頂點間有幾條連線,權重就是幾)
* @return 奧運五環圖的鄰接矩陣
*/
public static int[][] initOlympicRingsMatrix() {
//生成奧運五環圖的鄰接矩陣
return new int[][] {
{0, 3, 0, 1, 0, 0, 0, 0},
{3, 0, 1, 0, 0, 0, 0, 0},
{0, 1, 0, 2, 0, 1, 0, 0},
{1, 0, 2, 0, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 2, 0, 1},
{0, 0, 1, 0, 2, 0, 1, 0},
{0, 0, 0, 0, 0, 1, 0, 3},
{0, 0, 0, 0, 1, 0, 3, 0}
};
}
/**
* 判斷一筆畫是否完成,也就是看是否
* 已經形成了歐拉回路
* @param matrix 一筆畫圖鄰接矩陣的中間狀態
* @return 是否已經完成
*/
public static boolean isFinished(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
//如果還有邊沒有走過
if (matrix[i][j] > 0) {
//判定爲未完成
return false;
}
}
}
//所有邊已走過,判定爲已完成
return true;
}
/**
* 深度拷貝Set<Integer>對象
* @param source 拷貝源
* @return 深度拷貝的副本
*/
public static List<Integer> copyList(List<Integer> source) {
//拷貝副本
List<Integer> copy = new ArrayList<>();
//加入所有元素
for (Integer item : source) {
copy.add(item);
}
//返回拷貝副本
return copy;
}
/**
* 深度拷貝鄰接矩陣二維數組對象
* @param matrix 鄰接矩陣拷貝源
* @return 深度拷貝的鄰接矩陣副本
*/
public static int[][] copyMatrix(int[][] matrix) {
//拷貝副本
int[][] newMatrix = new int[matrix.length][matrix.length];
//複製所有值
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
newMatrix[i][j] = matrix[i][j];
}
}
//返回拷貝副本
return newMatrix;
}
/**
* 求解歐拉回路,探索一筆畫路徑
* @param matrix 當前的鄰接矩陣
* @param nextVertex 下一步的路徑
* @param route 當前的路線圖
* @param vertexName 頂點名稱字符串數組
*/
public static void runEulerCircuit(int[][] matrix, int nextVertex, List<Integer> route, String[] vertexName) {
//先複製一份當前的鄰接矩陣
int[][] newMatrix = copyMatrix(matrix);
//當前駐足的頂點
int currentVertex = route.get(route.size() - 1);
//走過nextVertex,更新鄰接矩陣
//更新方式爲邊權重減1,兩邊通路都要減
newMatrix[currentVertex][nextVertex] --;
newMatrix[nextVertex][currentVertex] --;
//再複製一份路徑圖
List<Integer> newRoute = copyList(route);
//路徑圖加入nextVertex
newRoute.add(nextVertex);
//找到nextVertex的所有下步頂點
List<Integer> nextList = new ArrayList<>();
//收集下一步可達的頂點
for (int i = 0; i < newMatrix.length; i++) {
if (newMatrix[nextVertex][i] > 0) {
nextList.add(i);
}
}
//如果有下一個可達的頂點
if (nextList.size() > 0) {
//遍歷所有的下一步頂點
for (Integer next : nextList) {
//遞歸調用本方法,繼續往下求解
runEulerCircuit(copyMatrix(newMatrix), next, copyList(newRoute), vertexName);
}
//如果現在已經沒有任何可達頂點了
} else {
//如果已經完成了一筆畫,求解了歐拉回路
if (isFinished(newMatrix)) {
//字符串路徑集合
List<String> solutionRoute = new ArrayList<>();
//根據頂點名稱字符串數組,得到字符串的路徑
for (Integer vertexIndex : newRoute) {
solutionRoute.add(vertexName[vertexIndex]);
}
//整理路徑信息
String solution = solutionRoute.toString()
.replaceAll("\\[", "")
.replaceAll("]", "")
.replaceAll(", ", " -> ");
//打印求得的一筆畫路徑(歐拉回路)
System.out.println("找到奧運五環一筆畫路徑:" + solution);
}
}
}
/**
* 求解歐拉回路,探索一筆畫路徑的驅動程序
* @param matrix 一筆畫圖的鄰接矩陣
* @param startVertex 開始頂點,如果有且僅有兩個度爲三的頂點,
* 則必須以這兩個頂點之一作爲起點
* @param vertexName 頂點名稱字符串數組
*/
public static void runEulerCircuit(int[][] matrix, int startVertex, String[] vertexName) {
//路徑
List<Integer> route = new ArrayList<>();
//從起點出發
route.add(startVertex);
//下一個頂點的集合
List<Integer> nextVertexList = new ArrayList<>();
//找到下一個可達的頂點
for (int i = 0; i < matrix.length; i++) {
if (matrix[startVertex][i] > 0) {
nextVertexList.add(i);
}
}
//遍歷所有下一步可達頂點
for (Integer nextVertex : nextVertexList) {
//多分支遞歸調用求解歐拉回路的算法程序
runEulerCircuit(matrix, nextVertex, route, vertexName);
}
}
/**
* 運行歐拉回路求解算法,解出奧運五環的所有一筆畫路徑
* @param args
*/
public static void main(String[] args) {
//奧運五環圖的初始鄰接矩陣
int[][] matrix = initOlympicRingsMatrix();
//奧運五環圖的頂點名稱字符串數組
String[] vertexName = new String[]{"A", "B", "C", "D", "E", "F", "G", "H"};
//從A點出發,運行算法,解出奧運五環的所有一筆畫路徑
runEulerCircuit(matrix, 0, vertexName);
}
}
運行上面代碼的main方法,控制檯輸出所有的奧運五環的一筆畫畫法路徑(大家感興趣可以自己嘗試照着畫一下),算法測試通過:
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> B -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> D -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> A -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> C -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> B -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> D -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> F -> E -> F -> G -> H -> G -> H -> E -> D -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> F -> E -> H -> G -> H -> G -> F -> E -> D -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> C -> F -> G -> H -> G -> H -> E -> F -> E -> D -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> E -> F -> E -> H -> G -> H -> G -> F -> C -> D -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> E -> F -> G -> H -> G -> H -> E -> F -> C -> D -> C -> B -> A -> B -> A
找到奧運五環一筆畫路徑:A -> D -> E -> H -> G -> H -> G -> F -> E -> F -> C -> D -> C -> B -> A -> B -> A