1736年欧拉解决了哥尼斯堡七桥问题。他在这一具体问题的基础上进一步研究,最终找到了一个简便的原则可以鉴别一个图(多重图)能否一笔画成。
本文中,笔者使用布尔矩阵来存储一个无向图,并结合集合论中“传递闭包”的概念给出了一种欧拉图的判定方法。
本文旨在给初学者提供一种可行解,第一次发文,笔者技艺不精,若文章中有错误之处,还望各位同仁能够海涵,希望与大家共同进步。
一些概念的解释:
包含图的所有顶点和所有边的闭迹成为欧拉闭迹。
存在一条欧拉闭迹的图成为欧拉图。
若 R R R是集合 X X X上的一个二元关系,则 X X X上的所有包含 R R R的传递关系的交称为 R R R的传递闭包,用 R + {R\mathop{{}}\nolimits^{{+}}} R+表示。即: R + = ⋂ R ⊆ R ’ R ’ ( 其中 R ’传递 ) {R\mathop{{}}\nolimits^{{+}}={\mathop{ \bigcap }\limits_{{R \subseteq R\text{'}}}{{R\text{'}}}} \left( \text{其}\text{中}R\text{'}\text{传}\text{递} \right) } R+=R⊆R’⋂R’(其中R’传递)亦即 R + {R\mathop{{}}\nolimits^{{+}}} R+是包含 R R R的传递关系中最小的那个。
Warshall算法是1962年由Warshall提出的一种计算关系传递闭包的十分有效的算法,将在下文详细说明。
注:本文中说的图都是指无向图
欧拉图的判断
在王义和编著的《离散数学引论》(第三版)中,给出了一个图 G G G是欧拉图的充要条件:
图 G G G是一个欧拉图当且仅当 G G G是连通的且每个顶点的度都是偶数。
因此,欧拉图的判断可分为两步:先判断图是否连通,再依次检查每一个顶点的度是否为偶数即可。
布尔矩阵与无向图
在集合论中,我们曾经使用布尔矩阵来存储一个二元关系。一个无向图也可看做非空顶点集 V V V上的一个反自反且对称的二元关系,因此也可使用布尔矩阵来存储一个无向图。
连通图与传递闭包
图 G G G是一个连通图,当且仅当图中任两个不同顶点间至少有一条路连接。
设图
G
=
(
U
,
V
)
G=(U,V)
G=(U,V)是一个连通图,
R
⊆
V
×
V
R\subseteq V\times V
R⊆V×V是图
G
G
G对应的二元关系,
R
+
{R\mathop{{}}\nolimits^{{+}}}
R+是关系
R
R
R的传递闭包。对
∀
u
,
v
∈
V
{ \forall u,v \in V}
∀u,v∈V,
如果
u
,
v
u,v
u,v邻接,则
(
u
,
v
)
,
(
v
,
u
)
∈
R
,
(u,v),(v,u)\in R,
(u,v),(v,u)∈R, 又
R
⊆
R
+
R\subseteq R^+
R⊆R+ 从而
(
u
,
v
)
,
(
v
,
u
)
∈
R
+
(u,v),(v,u)\in {R\mathop{{}}\nolimits^{{+}}}
(u,v),(v,u)∈R+;
如果
u
,
v
u,v
u,v不邻接,由于
G
G
G 是连通图,
u
,
v
u,v
u,v之间必存在一条路
u
,
u
1
,
u
2
,
.
.
.
.
.
.
,
u
n
,
v
{u,u\mathop{{}}\nolimits_{{1}},u\mathop{{}}\nolimits_{{2}},......,u\mathop{{}}\nolimits_{{n}},v }
u,u1,u2,......,un,v
那么序对
(
u
,
u
1
)
,
(
u
1
,
u
2
)
,
.
.
.
.
.
.
,
(
u
n
,
v
)
∈
R
(u,u_1),(u_1,u_2),......,(u_n,v)\in R
(u,u1),(u1,u2),......,(un,v)∈R,而
R
+
R^+
R+又是包含
R
R
R的最小传递关系,因此
(
u
,
v
)
∈
R
+
(u,v)\in R^+
(u,v)∈R+,由对称性,
(
v
,
u
)
∈
R
+
(v,u)\in R^+
(v,u)∈R+。
而对于图
G
G
G中某点
u
u
u自身,
u
u
u不可能是孤立点,因此一定有某点
v
v
v与之邻接,因此
(
u
,
v
)
,
(
v
,
u
)
∈
R
(u,v),(v,u)\in R
(u,v),(v,u)∈R,从而
(
u
,
u
)
∈
R
+
(u,u)\in R^+
(u,u)∈R+。
由此我们可以得出结论:一个连通图自身对应的二元关系 R R R 的传递闭包 R + R^+ R+ 包含了顶点集 V V V上的所有二元序对。这在 R + R^+ R+的布尔矩阵中表现为:所有位置上的值均为1,没有出现0的位置。如果 R + R^+ R+某一个位置上的值为0,那么说明这个位置对应的两个顶点之间必无路连接。
因此,判断一个图是否是无向图,只需求出它的传递闭包,再遍历所有位置看是否有0即可。
下面的问题就只剩下,如何求解传递闭包?
Warshall算法
在王义和编著的《离散数学引论》(第三版)中,给出了传递闭包 R + R^+ R+的一个计算公式:
设 X X X为 n n n元集, R R R为 X X X上的二元关系,则 R + = ⋃ i = 1 n R i 。 {R\mathop{{}}\nolimits^{{+}}={\mathop{ \bigcup }\limits_{{i=1}}^{{n}}{R\mathop{{}}\nolimits^{{i}}}}}。 R+=i=1⋃nRi。
在此基础上,Warshall提出了一种传递闭包的算法,在《离散数学引论》中也给出了其形式化描述:
(由矩阵
B
B
B计算其传递闭包
B
+
B^+
B+)
1. A ← B ; A\leftarrow B; A←B;
2. k ← 1 ; k \leftarrow1; k←1;
3. i ← 1 ; i \leftarrow 1; i←1;
4.如果 a i k = 1 a_{ik}=1 aik=1,则对 j = 1 , 2 , . . . , n j=1,2,...,n j=1,2,...,n 做 a i j ← a i j ∨ a k j ; a_{ij}\leftarrow a_{ij}\vee a_{kj}; aij←aij∨akj;
5. i ← i + 1 i\leftarrow i+1 i←i+1, i f i ≤ n t h e n g o t o 4 ; if\text{ }i \le n\text{ }then\text{ }goto\text{ }4; if i≤n then goto 4;
6. k ← k + 1 k\leftarrow k+1 k←k+1, i f k ≤ n t h e n g o t o 3 e l s e 停 ; {if\text{ }k \le n\text{ }then\text{ }goto\text{ }3\text{ }else\text{ }\text{停}}; if k≤n then goto 3 else 停;
这个算法结束时, A A A中就是矩阵 B + B^+ B+中的全部元素。
程序代码
对一个图 G G G,在程序中只需利用Warshall算法求出它的传递闭包,再检查所有位置中是否有0即可判断这个图的连通性。如果是连通图,在此基础上再检查每一个顶点的度的奇偶性即可判断这个图是否是欧拉图。
需要注意的是,存储无向图时,布尔矩阵应该表示为一个对称的、主对角线上全是0的矩阵。
最后在此附上没有使用C99特性的C语言代码:
/*程序功能:输入图G,判断G是否是欧拉图
*程序输入:第1行包括两个整数N和M,分别表示图G中的顶点个数和边的个数(N<100)
* 接下来的M行中每行包括两个整数,表示邻接的两个点的序号
*程序输出:如果G是欧拉图,则输出Yes,否则输出No
*/
#include <stdio.h>
#define MAX_SIZE 100
//华沙算法判断图是否连通,连通返回1,否则返回0
//传入参数为待判定的图的布尔矩阵和矩阵的大小
int isConnected(int graph[MAX_SIZE][MAX_SIZE], int n)
{
int i, j, k;
int a[MAX_SIZE][MAX_SIZE];
//先拷贝矩阵graph到a中,防止后续操作修改原矩阵
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i][j] = graph[i][j];
}
}
//华沙算法求传递闭包,结果放在矩阵a中
for (k = 0; k < n; k++)
{
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (a[i][k])
{
a[i][j] = a[i][j] || a[k][j];
}
//a[i][j] = a[i][j] || (a[i][k] && a[k][j]);
}
}
}
//连通图对应关系的传递闭包应该全1
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (a[i][j] == 0)
{
return 0;
}
}
}
return 1;
}
int main()
{
int N, M;
int i, j;
int Graph[MAX_SIZE][MAX_SIZE] = {0};
int v1, v2; //临时存储两个顶点序号
int deg[MAX_SIZE] = {0}; //存储每个顶点的度
printf("Input the number of dots and edges:\n");
scanf("%d%d", &N, &M);
printf("Input the edges:\n");
for (i = 0; i < M; i++)
{
scanf("%d%d", &v1, &v2);
Graph[v1-1][v2-1] = Graph[v2-1][v1-1] = 1;
}
//先判断图G是否连通
if (!isConnected(Graph, N))
{
printf("No\n");
return 0;
}
//再判断图G中每个顶点的度是否均为偶数
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++)
{
deg[i] += Graph[i][j];
}
if (deg[i] % 2 == 1) //如果有度为奇数的点直接输出No
{
printf("No\n");
return 0;
}
}
printf("Yes\n");
return 0;
}
参考文献:王义和《离散数学引论》(第三版)