地圖着色,需要每一個區域都使用一種顏色來進行填充,然後爲了與相鄰接壤的區域分開,就要求兩個接壤的區域需要使用不同的顏色。四色定理的意思是,最多只需要四種顏色,就可以爲所有的地圖進行全部區域着色,且任意兩個接壤的區域都是不同的顏色。在四色定理的指導下,我用Java來實現求解任意一副地圖的所有可行的區域着色方案的算法,並求解下面這幅示例地圖的所有着色方案:
算法思路:
- 將所有的區域進行編號,從0開始,然後用一個長度爲區域總數的數組記錄,稱爲顏色方案數組
- 顏色方案數組的下標是區域編號,值是使用的顏色,代號爲1、2、3、4四種顏色。初始化全部是0,也就是顏色0代表未着色,如果大於0,代表已着色,值就是顏色
- 創建無權無向圖,所有區域映射圖的頂點,如果兩個區域是接壤的,則對應圖的兩個頂點之間就是連通的,然後求出該無權無向圖的記錄有地圖區域接壤關係的鄰接矩陣
- 0區域默認使用1號顏色,然後找到下一個區域,根據接壤關係的鄰接矩陣和顏色方案數組,找到下一個區域的所有接壤區域已經使用過的顏色集,再反向求出可使用的顏色集
- 如果下一個區域的可使用顏色集爲空集,則當前遞歸分支結束。如果不爲空集,則遍歷所有可用顏色,然後遞歸調用,一個可用顏色爲一個遞歸分支,着色下一個區域……
- 遞歸方法在着色後進行判斷,如果剛剛着了色的區域已經是最後一個區域了,則表示程序已經找到了地圖着色方案。然後程序輸出顏色方案數組,以展示可行的地圖着色方案
第一步:將所有的區域進行編號,從0開始
第二步:創建無權無向圖,所有區域映射圖的頂點,如果兩個區域是接壤的,則連通對應圖的兩個頂點
3、根據上面記錄有地圖區域接壤關係的無權無向圖,求出該圖的鄰接矩陣。該鄰接矩陣也是算法輸入的數據來源
下面是我用Java實現的求解上面示例圖的着色方案的算法。該算法的思想和精髓已經在下面的代碼和其間的詳盡註釋中:
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author LiYang
* @ClassName FourColorTheorem
* @Description 四色定理探索實踐類
* @date 2019/12/7 22:32
*/
public class FourColorTheorem {
//根據四色定理,最多隻使用四種顏色,就可以爲任何地圖
//着色,並且保證相鄰接壤的區域必須用不同的顏色
//---------------------------------------------
//注意,如果你的地圖足夠特別,也是有可能三種,甚至更少
//的顏色種類就完成着色,此時可以將下面的數字改小,然後
//查看控制檯是否有結果輸出,有結果輸出則表示可行
private static final int MIN_NECESSARY_MAP_COLOR = 4;
/**
* 返回示例地圖接壤信息的鄰接矩陣
* @return 示例地圖接壤信息的鄰接矩陣
*/
public static int[][] initMapMatrix() {
//直接返回記錄示例地圖接壤信息的鄰接矩陣
//要運行自己的地圖,可以修改此鄰接矩陣
return new int[][] {
{0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0},
{1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0},
{0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1},
{0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0},
{0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1},
{0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1},
{0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0}
};
}
/**
* 深拷貝數組
* @param source 數組拷貝源
* @return 數組副本
*/
public static int[] copyArray(int[] source) {
//創建新的數組
int[] copy = new int[source.length];
//拷貝賦值新數組
System.arraycopy(source, 0, copy, 0, source.length);
//返回深拷貝的副本
return copy;
}
/**
* 地圖區域着色算法的遞歸執行方法
* @param nextArea 着色區域
* @param color 着色使用的顏色
* @param colorPlan 着色計劃記錄數組
* @param matrix 區域接壤的鄰接矩陣
*/
public static void coloringMap(int nextArea, int color, int[] colorPlan, int[][] matrix) {
//將當前區域着色
colorPlan[nextArea] = color;
//如果已經全部着色,則打印結果
if (nextArea == colorPlan.length - 1) {
//打印當前遞歸分支的着色方案
System.out.println("找到着色方案:" + Arrays.toString(colorPlan));
//結束當前遞歸分支
return;
}
//當前區域更新爲下一個區域
//準備爲下一個區域進行着色
nextArea ++;
//下一個區域的可用顏色集
Set<Integer> availableColor = new HashSet<>();
//初始化可用顏色集
for (int i = 1; i <= MIN_NECESSARY_MAP_COLOR; i++) {
//先全部加入,再用排除法去掉周邊接壤顏色
availableColor.add(i);
}
//遍歷鄰接矩陣,找到下一個區域的所有接壤區域
for (int i = 0; i < matrix.length; i++) {
//如果當前區域接壤,且已經着色
if (matrix[nextArea][i] > 0 && colorPlan[i] > 0) {
//將接壤的已用顏色剔除
availableColor.remove(colorPlan[i]);
}
}
//遍歷下一個區域的所有可用顏色
for (int available : availableColor) {
//分別遞歸調用本算法,嘗試下一個區域着所有可用顏色集
coloringMap(nextArea, available, copyArray(colorPlan), matrix);
}
}
/**
* 地圖區域着色算法的驅動方法
* @param matrix 地圖接壤的鄰接矩陣
*/
public static void coloringMap(int[][] matrix) {
//我們從0號區域開始,從哪個區域開始都一樣
int nextArea = 0;
//顏色代號是1-4,本質是等價可互換的,用1開始就行
int color = 1;
//初始化着色方案
int[] colorPlan = new int[matrix.length];
//調用地圖區域着色算法的遞歸執行方法
coloringMap(nextArea, color, colorPlan, matrix);
}
/**
* 運用四色定理,運行示例地圖着色的算法
* @param args
*/
public static void main(String[] args) {
//獲得記錄示例地圖接壤關係的鄰接矩陣
int[][] matrix = initMapMatrix();
//運行示例地圖着色的算法,並從控制檯
//查看打印的着色方案
coloringMap(matrix);
}
}
運行FourColorTheorem類的main方法,控制檯輸出了很多可行的示例圖着色方案!下面是我隨意摘選的一部分示例圖着色方案:
找到着色方案:[1, 2, 2, 3, 1, 1, 2, 3, 4, 2, 3]
找到着色方案:[1, 3, 2, 4, 2, 1, 3, 4, 2, 3, 4]
找到着色方案:[1, 4, 2, 4, 2, 1, 4, 3, 2, 1, 4]
找到着色方案:[1, 4, 4, 3, 3, 2, 4, 1, 3, 2, 1]
接下來我們驗證算法求出的示例圖着色方案。1號我們用紅色,2號用黃色,3號用藍色,4號用粉色,驗證上面我隨意篩選的四種示例圖着色方案,地圖着色後表明,該算法測試通過: