关于前面的树的学习已经告一段落了,从这一篇起我们要开始新的篇章——开始图的学习,但在正式学习图之前,我们先来了解一个有趣的,关于图的问题:哥尼斯堡七桥问题。
1. 哥尼斯堡七桥问题
图1
在18世纪初普鲁士的哥尼斯堡小镇上有一条河穿过,河上有两个小岛,有七座桥把两个岛与河岸联系起来。
于是有人提出问题:一个步行者怎样才能不重复,不遗漏地一次走完七座桥,最后回到出发点 。
这个问题提出后,引发了很多人的兴趣,于是小镇上的居民开始思考这个问题,进行试验,但是在之后很长的一段时间里,这个问题一直都没有得到解决。
其实对于这个问题,利用普通数学知识,每座桥均走一次,那这七座桥所有的走法一共有5040种,如果每种走法都走一遍的话需要花费很长时间,但怎么才能找到成功走过每座桥而不重复的路线呢?因而形成了著名的“哥尼斯堡七桥问题”。
于是就有学生给天才数学家欧拉写信,请他帮忙解决这一问题。1736年,在经过一年的研究之后,29岁的欧拉提交了《哥尼斯堡七桥》的论文,圆满解决了这一问题,同时开创了数学新一分支 ——— 图论(图论不光有在计算机中应用,而且在其他学科中也有着广泛的应用,向伟大的欧拉数学家致敬!)。
2. 欧拉解题路线
图2
欧拉将哥尼斯堡七桥问题抽象出来,把每一块陆地考虑成一个点,连接两块陆地的桥以线表示,用A、B、C、D四个点表示为哥尼斯堡的四个区域,并由此得到了如图一样的几何图形,然后再进行形式化定义。
欧拉在根据上面的几何图形的图性质,解决哥尼斯堡七桥问题的结论是这样的: 如果通奇数桥(七座桥是奇数桥)的地方多于两个,则不存在欧拉回路; 如果只有两个地方通奇数桥,可以从这两个地方之一出发,找到欧拉回路;如果没有一个地方通奇数桥,则无论从哪里出发,都能找到欧拉回路。结论:通奇数桥的地方为0个或2个,有欧拉回路
。
比如在上图中连接A的路线有5条,连接B的路线有3条,连接C和D的路线也有3条。所以通奇数桥的地方不是0个,也不是2个,是没有欧拉回路的,因此欧拉根据这样的性质得出:没有任何一种走法可以从某地出发,不遗漏地一次走完七座桥,再回到原点。也就是说,这个问题无解。
3. 用计算机求解图问题
其实对于哥尼斯堡七桥问题,我们可以依次计算图中与每个节点相关联的边的个数(节点的度),根据度为奇数的节点个数判定是否存在欧拉回路。
数据表示——数据结构:
设邻接矩阵arc[n][n]这样的存储结构来描述图中每个点的桥的个数(边的个数)
图3
在邻接矩阵中:
第1行第1列的0代表A到A的边的个数为0
第1行第2列的2代表A到B的边的个数为2
第1行第3列的2代表A到C的边的个数为2
第1行第4列的1代表A到D的边的个数为1
第2行第1列的2代表B到A的边的个数为2
第2行第2列的0代表B到B的边的个数为0
第2行第3列的0代表B到C的边的个数为0
第2行第4列的1代表B到D的边的个数为1
第三行,以此类推……
第四行,以此类推……
数据处理——算法:
1. 通奇数桥的顶点个数count初始化为0;
2. 下标 i 从0 ~ n – 1重复执行下述操作:
2.1 计算矩阵arc[n][n]第i行元素之和degree;
2.2 如果degree为奇数,则count++;
3. 如果count等于0或2,则存在欧拉回路;否则不存在欧拉回路;
算法实现:
int main(void)
{
//邻接矩阵arc
char ch = 'A';
int arc[4][4] = {
{0,2,2,1},
{2,0,0,1},
{2,0,0,1},
{1,1,1,0}
};
int count = 0;
int i;
int j;
int degree = 0;
for(i = 0; i < 4; i++)
{
//计算矩阵arc[n][n]第i行元素之和degree
for(j = 0; j < 4; j++)
{
degree += arc[i][j];
}
printf("%c点的度为:%d\n" , ch++, degree);
//如果degree为奇数,则count++
if(degree % 2 == 1)
{
count++;
}
degree = 0;
}
//如果count等于0或2,则存在欧拉回路;否则不存在欧拉回路
if(count == 0 || count == 2)
{
printf("存在欧拉回路\n");
}
else
{
printf("不存在欧拉回路\n");
}
return 0;
}
测试结果:
在这一篇中我们简单介绍了哥尼斯堡七桥问题,以及用计算机求解图问题,而这也是我们接下来要学习的数据结构——图。