地图着色之四色问题的一般解(C语言)

306 阅读7分钟

by:御剑飞行

四色定理(世界近代三大数学难题之一),又称四色猜想

、四色问题,是世界三大数学猜想之一。四色问题的内容是“任何一张地图只用四种颜色就能使具有共同边界的国家着上不同的颜色。”也就是说在不引起混淆的情况下一张地图只需四种颜色来标记就行。

用数学语言表示即“将平面任意地细分为不相重叠的区域,每一个区域总可以用 0 1 2 3 这四个数字之一来标记而不会使相邻的两个区域得到相同的数字。”这里所指的相邻区域是指有一整段边界是公共的。如果两个区域只相遇于一点或有限多点就不叫相邻的。

四色猜想看着是一个带有数学游戏性质的孤立的问题,可是它却创造出图论许多新的分支。数学家的本事在于他们能够把复杂的事物变成简单的对象。从四色问题就可以做这样的化简:一个区域不妨看成一个点,任何两个区域或者相邻(也就是公用一条边界),或是不相邻。如果代表两个区域地点相邻,那么我们就在两点之间连上一条线,否则就不连线。这样的结构就称为图。四色问题也就变成无向图的顶点着色的问题,也就是两顶点如果有线相连,则必须涂上不同颜色。

Mapmost SDK利用了四色定理对地形瓦片进行ID标注。为了方便教学,便于大家理解四色定理的算法,下面我用C语言图论中的邻接矩阵

实现一下,虽然这并不是Mapmost SDK中的采用的代码(实际上Mapmost SDK自研了一套[集成算法

](zhida.zhihu.com/search?cont…

_一、_教学源代码

这里将地图中的地块用计算机图论中的无向图表示,每个地块都是无向图中的一个节点。在程序结束时,每个节点都会对应四种颜色(分别标号0,1,2,3)的一个。比较值得注意的是,在一个无向图中,每个节点对应的四种颜色标记的解不是唯一的。按照当前算法,这与无向图中从哪个节点开始标记有关。

由于这里的算法采用了邻接矩阵(其实就是二维数组方阵),因此你可以对一个无向图,从任意结点开始标号。标号完成后,如果要添加节点之间的联通关系,非常简单,就是执行

Add(邻接矩阵对象指针, nodeIdx1, nodeIdx2);

程序会将nodeIdx1和nodeIdx2对应的节点之间创建一条连接线,代表两个节点联通。

具体源代码如下:

#include<stdio.h>
#include<stdlib.h>

//邻接矩阵结构体,Vertices代表网络中节点的数量,A代表结构体指针的指针
typedef struct graph {
    int Vertices;
    int** A;
}Graph;

//初始化邻接矩阵,将矩阵的中的每个元素都初始化为0。
//g代表邻接矩阵的指针,n是邻接矩阵的阶数,也就是网络中节点的数量。
void CreateGraph(Graph *g, int n ) {
    int i, j;
    g->Vertices = n;
    g->A = (int**)malloc(n*sizeof(int*));
    for (i = 0; i < n;i++) {
        g->A[i] = (int*)malloc(n*sizeof(int));
        for (j = 0; j < n;j++) {
            g->A[i][j] = 0;
        }
    }
}

//添加节点之间的联通关系,将邻接矩阵u行v列、v行u列置为1即可。
void Add(Graph*g, int u,int v) {
    if (u < 0 || v < 0 || u > g->Vertices - 1 || v>g->Vertices - 1 || u == v || g->A[u][v] != 0) {
        printf("BadInput\n");
    }
    else {
        g->A[u][v] = 1;
        g->A[v][u] = 1;
    }
}

//删除节点之间的联通关系,将邻接矩阵u行v列、v行u列置为0即可。
void Delete(Graph *g, int u, int v) {
    if (u < 0 || v < 0 || u > g->Vertices - 1 || v>g->Vertices - 1 || u == v || g->A[u][v] == 0) {
        printf("BadInput\n");
    }
    else {
        g->A[u][v] = 0;
        g->A[v][u] = 0;
        printf("Delete Successfully\n");
    }
}

//判断节点之间的联通关系是否存在,就是判断邻接矩阵u行v列、v行u列置是否为1。
void Exist(Graph* g, int u, int v) {
    if (u < 0 || v < 0 || u > g->Vertices - 1 || v>g->Vertices - 1 || u == v || g->A[u][v] == 0||g->A[v][u] == 0) {
        printf("No Existance\n");
    }
    else if(g->A[u][v] == 1 && g->A[v][u] == 1){
        printf("Element Exists!\n");
    }
}

//打印邻接矩阵方阵
void PrintMatrix(Graph* g) {
    int i,j;
    for (i = 0; i < g->Vertices;i++) {
        for (j = 0; j < g->Vertices;j++) {
            printf("%-5d",g->A[i][j]);
        }
        printf("\n");
        printf("\n");
    }
    printf("***************\n");
}

//利用四色,给地图着色
int MapColor(Graph* g) {
    int i, j, k, m, n;
    int* verticesArray = (int*)malloc(g->Vertices * sizeof(int));
    int colorPool[4] = { 0 };
    for (k = 0; k < g->Vertices; k++)
    {
        verticesArray[k] = -1;
    }
    for (i = 0; i < g->Vertices; i++) {
        for (m = 0; m < 4; m++)
        {
            colorPool[m] = 0;
        }
        if (verticesArray[i] == -1)
        {
            for (j = 0; j < g->Vertices; j++) {
                if (colorPool[0] == 1 && colorPool[1] == 1 && colorPool[2] == 1 && colorPool[3] == 1)
                {
                    printf("颜色池全部用完了,四色定理失效!基本不可能发生");
                    return 0;
                }
                if (g->A[i][j] ==1 && verticesArray[j] != -1)
                {
                    colorPool[verticesArray[j]] = 1;
                }
            }
            for (m = 0; m < 4; m++)
            {
                if (colorPool[m] == 0)
                {
                    verticesArray[i] = m;
                    colorPool[m] = 1;
                    break;
                }
            }
        }
        else {
            continue;
        }

        for (j = 0; j < g->Vertices; j++) {
            if (colorPool[0] == 1 && colorPool[1] == 1 && colorPool[2] == 1 && colorPool[3] == 1)
            {
                break;
            }
            else {
                if (g->A[i][j] == 1 && verticesArray[j] == -1)
                {
                    for (m = 0; m < 4; m++)
                    {
                        if (colorPool[m] == 0)
                        {
                            verticesArray[j] = m;
                            colorPool[m] = 1;
                            break;
                        }
                    }
                }
            }
        }
    }
    for (int n = 0; n < g->Vertices; n++)
    {
        printf("%-5d		%-5d\n", n, verticesArray[n]);
    }
    free(verticesArray);
    return 1;
}

void main() {
    Graph* g;//创建邻接矩阵对象指针
    g = (Graph*)malloc(sizeof(Graph));//分配空间
    CreateGraph(g, 8);//初始化
    //PrintMatrix(g);
    Add(g, 4, 0);//添加节点间联通关系
    Add(g, 4, 1);//添加节点间联通关系
    Add(g, 4, 2);//添加节点间联通关系
    Add(g, 4, 3);//添加节点间联通关系
    Add(g, 4, 6);//添加节点间联通关系
    Add(g, 4, 5);//添加节点间联通关系
    Add(g, 1, 0);//添加节点间联通关系
    Add(g, 1, 2);//添加节点间联通关系
    Add(g, 3, 2);//添加节点间联通关系
    Add(g, 3, 6);//添加节点间联通关系
    Add(g, 5, 6);//添加节点间联通关系
    Add(g, 5, 0);//添加节点间联通关系
    Add(g, 7, 0);//添加节点间联通关系
    Add(g, 7, 5);//添加节点间联通关系
    //Delete(g, 1, 0);
    PrintMatrix(g);//打印
    MapColor(g);//着色
    free(g);//释放空间
}

_二、_源代码运行结果

运行结果:

以下矩阵就是我们创建好的用来表示无向图的邻接矩阵,这是一个用来表示无向图的对称方阵,对应的无向图如图1所示。0 1 0 0 1 1 0 11 0 1 0 1 0 0 00 1 0 1 1 0 0 00 0 1 0 1 0 1 01 1 1 1 0 1 1 01 0 0 0 1 0 1 10 0 0 1 1 1 0 01 0 0 0 0 1 0 0

图1 示例无向图

顶点颜色

第一列是顶点列表,第二列是顶点对应的节点颜色。在实际着色中,就是该地块对应的颜色。顶点 颜色

0 -> 0

1 -> 1

2 -> 0

3 -> 3

4 -> 2

5 -> 3

6 -> 0

7 -> 1

图2 无向图表示的地块的着色结果

图2中,地图颜色集合是{0,1,2,3},每个数字对应不同的颜色。黑色的数字代表地图中的地块标号,黑色线段代表地块是相邻的。黑色数字对应的彩色数字就是着色完成后,地块对应的地图颜色,这里就是地图颜色集合{0,1,2,3}中的一个带颜色的数字。这只是一个很简单的示例无向图,有意者可以拿更复杂的无向图去尝试下。

Mapmost SDK中也使用了四色定理设置多种风格的地图。大家可以直接调用Mapmost SDK API,一键应用自己喜欢的地图。

点击链接跳转Mapmost官网体验,现在申请使用可获得一个月的免费权限,可以自由开发、自由定制你的工程!