对于一个图中是否存在一条哈密顿路,没有可靠的充分必要条件(貌似邻接矩阵恒式可以?),因此求哈密顿路是一个NP问题,一般要使用搜索和状压dp求解,但汉密尔顿回路的存在有许多充分条件,即当图满足某些特定性质的时候,汉密尔顿回路一定存在,而且可以根据一些算法构造出来。
1.Dirac定理:设一个无向图中有 N 个节点,若所有节点的度数都大于等于 N/2,则汉密尔顿回路一定存在。
(“N/2” 中的除法不是整除,而是实数除法,该条件中的 “N/2” 等价于 “⌈N/2⌉”)
证明:首先可以证明图一定是连通的。设 d(v) 表示节点 v 的度数。对于任意两个节点 u、 v,若它们不相邻,则可能和它们相邻的节点共有 N - 2 个,而 d(u) + d(v) ≥ N/2 + N/2 ≥ N,那么根据鸽巢原理,肯定存在一个节点与 u 和 v 都相邻。即证,任何两个节点之间都是连通的。
构造方法:
1. 任意找两个相邻的节点 S 和 T,在它们基础上扩展出一条尽量长的没有重复节点的路径。也就是说,如果 S 与节点 v 相邻,而且 v 不在路径 S → T 上,则可以把该路径变成 v → S → T,然后 v 成为新的 S。从 S 和 T 分别向两头扩展,直到无法扩为止,即所有与 S 或 T 相邻的节点都在路径 S → T 上。
2. 若 S 与 T 相邻,则路径 S → T 形成了一个回路。
3. 若 S 与 T 不相邻,可以构造出一个回路。设路径 S → T 上有 k + 2 个节点,依次为 S、 v1、 v2…… vk 和 T。可以证明存在节点 vi, i ∈ [1, k),满足 vi 与 T 相邻,且 vi+1与 S 相邻。证明方法也是根据鸽巢原理,既然与 S 和 T 相邻的点都在该路径上,它们分布的范围只有 v1 ∼ vk 这 k 个点, k ≤ N - 2,而 d(S) + d(T) ≥ N,那么可以想像,肯定存在一个与 S 相邻的点 vi 和一个与 T 相邻的点 vj, 满足 j < i。那么上面的命题也就显然成立了。找到了满足条件的节点 vi 以后,就可以把原路径变成 S → vi+1 → T → vi → S,即形成了一个回路。
4. 现在我们有了一个没有重复节点的回路。如果它的长度为 N,则汉密尔顿回路就找到了。如果回路的长度小于 N,由于整个图是连通的,所以在该回路上,一定存在一点与回路以外的点相邻。那么从该点处把回路断开,就变回了一条路径。再按照步骤1的方法尽量扩展路径,则一定有新的节点被加进来。接着回到步骤 2。
在整个构造过程中,如果说每次到步骤 4 算是一轮的话,那么由于每一轮当中,至少有一个节点被加入到路径 S → T 中来,所以总的轮数肯定不超过 N 轮。实际上,不难看出该算法的复杂度就是 O(N^2),因为总共扩展了 N 步路径,每步扩展最多枚举所有的节点。
证明:对n作归纳法。n=2时,D的基图为K2,结论成立。设n=k时结论成立。现在设n=k+1.设V(D)={v1,v2,…,vk,vk+1}。令D1=D-vk+1,易知D1为k阶竞赛图,由归纳假设可知,D1存在哈密顿通路,设Г1=v'1v'2…v'k为其中一条。下面证明vk+1可扩到Г1中去。若存在v'r(1≤r≤k),有<v'i,vk+1>∈E(D),i=1,2,…,r-1,而<vk+1,v'r>∈E(D),见图(1)所示,则Г=v'1v'2…v'r-1vk+1v'r…v'k为D中哈密顿通路。否则,i∈{1,2,…,k},均有<v'i,vk+1>∈E(D),见下图所示,则Г=Г'∪<v'k,vk+1>为D中哈密顿通路。
构造:可以依次从1~N遍历所有的点。当v=1时成立。之后根据上述定理,每次循环开始时都维持着一条哈密顿路,直到循环结束。具体实现可以用链表来模拟。
poj 1776 Task Sequences构造哈密顿通路
- boolean map[][] = new boolean[maxn][maxn];
- int nxt[]=new int[maxn],ans[]=new int[maxn];
- void solve(int n){
- Arrays.fill(nxt, -1);
- int h=1;
- for(int i=2;i<=n;i++)
- if(map[i][h]){
- nxt[i]=h;
- h=i;
- }
- else{
- int pre=h,pos=nxt[h];
- while(pos!=-1&&!map[i][pos]){
- pre=pos;
- pos=nxt[pos];
- }
- nxt[pre]=i;
- nxt[i]=pos;
- }
- int cnt=0;
- for(int i=h;i!=-1;i=nxt[i])
- ans[++cnt]=i;
- }
- void run() throws IOException{
- while(in.nextToken()!=in.TT_EOF){
- int n=(int)in.nval;
- for(int i=1;i<=n;i++)
- for(int j=1;j<=n;j++)
- map[i][j]=(nextInt()==1);
- solve(n);
- System.out.println("1\n"+n);
- for(int i=1;i<n;i++)
- System.out.print(ans[i]+" ");
- System.out.println(ans[n]);
- }
- }
判断哈密顿圈只需枚举起点,将构造方法稍作改动(注意只有一个点时特判),显然竞赛图有哈密顿通路不一定有哈密顿回路。
hdu 3414 Tour Route 构造哈密顿回路
- boolean map[][] = new boolean[maxn][maxn];
- int nxt[] = new int[maxn], ans[] = new int[maxn];
- void solve(int start, int n) {
- Arrays.fill(nxt, -1);
- int h = start;
- for (int i = 1; i <= n; i++){
- if(i==start)
- continue;
- if (map[i][h]) {
- nxt[i] = h;
- h = i;
- } else {
- int pre = h, pos = nxt[h];
- while (pos != -1 && !map[i][pos]) {
- pre = pos;
- pos = nxt[pos];
- }
- nxt[pre] = i;
- nxt[i] = pos;
- }
- }
- int cnt = 0;
- for (int i = h; i != -1; i = nxt[i])
- ans[++cnt] = i;
- }
- void run() throws IOException {
- while (true) {
- int n = nextInt();
- if(n==0)
- break;
- for (int i = 1; i <= n; i++)
- for (int j = 1; j <= n; j++)
- map[i][j] = (nextInt() == 1);
- if(n==1)
- {
- System.out.println(1);
- continue;
- }
- boolean flag=true;
- for (int k = 1; k <= n; k++) {
- solve(k,n);
- if(!map[ans[n]][ans[1]])
- continue;
- for (int i = 1; i < n; i++)
- System.out.print(ans[i] + " ");
- System.out.println(ans[n]);
- flag=false;
- break;
- }
- if(flag)
- System.out.println(-1);
- }
- }
Dirac 定理: 设一个无向图中有 N 个节点,若所有节点的度数都大于等于 N/2,则汉密尔顿回路一定存在。注意,“N/2” 中的除法不是整除,而是实数除法。如果 N 是偶数,当然没有歧义;如果 N 是奇数,则该条件中的 “N/2” 等价于 “⌈N/2⌉”。
网上已经有证明的方法,比如:http://blog.csdn.net/weiguang_123/article/details/7830047
而我想从自己的理解用反证法来证明一下:
我们先假设N为偶数,如果偶数成立,那当为奇数的时候很明显(边 / 点)的值更大出现回路的可能性更大(这只是我的一种理解方式,其实也可以按照偶数的方式证明的)
一:首先可以证明图一定是连通的:
前提:设 d(v) 表示节点 v 的度数。对于任意两个节点 u、 v,若它们不相邻,则可能和它们相邻的节点共有 N - 2 个,而 d(u) + d(v) ≥ N/2 + N/2 ≥ N。
1:假设u和v不连通
2:则尽量使得u和v的度数连向不同的点(即尽量别出现某点同时和u,v点同时相连),那么将有(N - 2)/ 2的点和u连,(N - 2)/ 2的点和v连;
而d(u) = N / 2 = (N - 2) / 2 + 1;所以u至少还得连接一个已经被v连接的点,
3:存在一个点同时和u,v连接,那么u,v可达;
所以这样u和v就能通过这个点连通了。(同理:v会和已经被u连接好的点连接)
二:现在证明存在一个连接所有N个点的回路:
步骤1.先证明存在k(k <= N)个点的的哈密顿回路。
(1)任意找两个相邻的节点 S 和 T,在它们基础上扩展出一条尽量长的没有重复节点的路径。也就是说,如果 S 与节点 v 相邻,而且 v 不在路径 S → T 上,则可以把该路径变成 v → S → T,然后 v 成为新的 S。从 S 和 T 分别向两头扩展,直到无法扩为止,即所有与 S 或 T 相邻的节点都在路径 S → T 上。此时这条路径的点数为k 个。
(2)而 d(S) + d(T) ≥ N/2 + N/2 ≥ N >= k;只要证明S到T的路径上存在点vi和T相连,vj和S相连(i < j)那么i到j路径任意相连两点都能作为回路的始末点。
证明如下:
【1】假设与S相连的都在编号为2到k/2,T相连的是k/2到k - 1,这样就能保证不会存在点(i < j)vi和T相连,vj和S相连
【2】但是这样和S的度数最多为:k/2 - 1 <= N / 2 - 1,则d(S) <= N / 2 - 1和d(S) >= N / 2矛盾。
【3】所以必须和S相连的会有一个点大于k/2,证明完毕。
if(k = N)证明完毕;
else 跳到步骤2;
步骤2:
(1)由步骤1得到k个点的哈密顿回路。
(2)当存在第k+1点即不和S相连,又不和T相连,则必定和S到T间的点Vi相连,否则不连通,这样就可以断开k个点的回路Vi附近的边,形成新的S->T的路径,之后可以回到步骤1,直到k = N。
竞赛图:每对顶点之间都有一条边相连的有向图称为竞赛图
证明:n(n>=2)阶竞赛图一定存在哈密顿通路
归纳法:假设当1到k个点已经为哈密顿通路1-> 2 -> ....->k,再把Vk+1归并进去:
1:当假k个点中有相邻的两个点Vi->Vi + 1,出现Vi -> Vk+1,Vk+1 -> Vi+1,则Vk+1直接插入Vi->Vi+1
2:当假k个点中有相邻的两个点Vi->Vi + 1,出现Vi -> Vk+1,Vi+1 ->Vk+1,当往右追溯到k的话如果一直都是指向Vk+1的话,那必然出现Vk->Vk+1
否则会出现1的情况。
3:当假k个点中有相邻的两个点Vi->Vi + 1,出现 Vk+1 -> Vi,那就往左追溯到1,如果一直Vk+1指向vj(1 <= j <= i)的话,那必然会出现Vk+1 -> V1
否则会出现1的情况。
转载:http://blog.csdn.net/woshi250hua/article/details/7961869
备注:细心的你可能会注意到:我改了一点点:
- (isok[i | (1 << k)])
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4281
题目大意:给定n个地点的坐标和每个地点的权值,即一张图n个点,点有点权边有边权。现在裁判在点1,需要分配这些裁判到这些点去,已知每个裁判能够到点权之和不大于m,而且一个点不能由两个裁判访问。现在给出两个问题,1、最少几个裁判可以覆盖所有点 2、给定无数个裁判,怎么样访问这些点使得总边权和最小,裁判访问完必须回到1点,而且一个裁判访问的点权之和不能超过m。
解题思路: 昨天天津赛区的1004题,比赛的时候都想到了算法,就是不敢去敲,一直在想稳妥的算法,最终没有ac。
晚一点和其他学校的acmer交流聊到这题,就想着要不要去试下,如果不行的话明天去搜论文,因为这是很经典的mTSP问题。但是没想到竟然ac了,神奇得ac了,坑爹地ac了,复杂度sigma[C(n,i)*(2^i-1)],最坏情况下计算量也才2000多万,其实是我错了!!ac完想到的第一句话不是终于ac而是:尼玛,中山大学就喜欢这么暴力么?..
有可能不是正解,但还是写下思路,感觉两个问题都很经典。
第一问:求最少的裁判覆盖这些点,思路是先将2^n种地点的选择集合压缩成2^n个物品,物品的权值为集合内的点权之和,如果总和<=m,那么他是一种合法的组合,存起来。这样就得到tot种合法组合,对这tot种组合进行01背包,dp[i]表示容量为i时的最小费用,和常规的背包不同,但本质是一样的。状态转移方程:dp[i] = min(dp[i],dp[j]+1) (j为i的子集,i = j | state[k]并且j和state[k]没有交集,state[k]表示第k个合法物品)
PS:后来发现这一问其实用贪心就可以解决,每次都选最大的,直到本次不能选为止,看能选几次.
第二问:多旅行商问题即mTsp,感觉挺经典的,思路是将mtsp转化成普通的tsp,然后再将各个tsp合并成答案。先要O(2^n*n^2)的预处理得到np[i]表示一个裁判走的集合为i的所有地点又回到最初的点的最少权值和,然后np[i] = min(np[i],np[k|(1<<0)]+np[(i-k)|(1<<0)])(i必须包含0节点,因为子集可能不含0节点,所以要和1<<0或起来,这样才是将两个裁判所走的边权和合并)。
自己默念:
1.首先将能被一个人解决的集合i即可标记为1,即isok【i】=1,之后算出每个该集合访问完毕所需要的 最算时间np【i】,
(这个可以多开一个cost【j】【i】数组来表示:状态i,j最后接入的最小耗费值,这个和单旅行商的解决方案一样了);
2.最后mTSP和TSP的 区别就在于要将所有的np【i】合并:即np[i] = min(np[i], np[j | 1] + np[(i - j) | 1]);
值得注意的是这里合并后的的isok[i]可以等于0;因为这是 合并后的,即使一个人不能解决完i集合,但是多个人就可以!
- #include <stdio.h>
- #include <string.h>
- #include <math.h>
- #include <algorithm>
- using namespace std;
- #define MIN (1<<17)
- #define MAX 110000
- #define INF (1<<29)
- #define min(a,b) ((a)<(b)?(a):(b))
- int tot, ans1, ans2, n, m; //总合法物品数,第一、第二问答案
- int x[20], y[20], val[20]; //左边和点权
- int dp[MAX], state[MIN]; //第一问用到
- int map[20][20], isok[MIN]; //边权、合法物品集合
- int cost[17][MIN], np[MIN]; //第二问用到
- void Initial() {
- int i, j;
- tot = 0;
- memset(map, 0, sizeof (map));
- for (i = 0; i < (1 << n); ++i)
- dp[i] = np[i] = INF;
- for (i = 0; i <= n; ++i)
- for (j = 0; j < (1 << n); ++j)
- cost[i][j] = INF;
- cost[0][1] = 0;
- }
- int cmp1(int a, int b) {
- return a > b;
- }
- int Solve_Tanxin() {
- int i, j, mmin = INF;
- int tp[20], vis[20];
- for (i = 0; i < n; ++i)
- vis[i] = 0, tp[i] = val[i];
- sort(tp, tp + n, cmp1);
- for (i = 1; i <= n; ++i) {
- int rest = m;
- for (j = 0; j < n; ++j)
- if (!vis[j] && tp[j] <= rest)
- rest -= tp[j], vis[j] = 1;
- ;
- for (j = 0; j < n && vis[j] == 1; ++j);
- if (j == n) return i;
- }
- return INF;
- }
- void GetDist() {
- for (int i = 0; i < n; ++i)
- for (int j = i + 1; j < n; ++j) {
- double xx = x[i] - x[j];
- double yy = y[i] - y[j];
- xx *= xx, yy *= yy;
- map[i][j] = map[j][i] = ceil(sqrt(xx + yy));
- }
- }
- int ok(int x) {
- int sum = 0, i;
- for (i = 0; i < n; ++i)
- if (x & (1 << i)) sum += val[i];
- return sum <= m;
- }
- int TSP_Second() {
- int i, j, k;
- GetDist();
- for (i = 1; i < (1 << n); ++i) if (isok[i]) {
- for (j = 0; j < n; ++j) if (i & (1 << j)) {
- np[i] = min(np[i], cost[j][i] + map[j][0]);
- for (k = 0; k < n; ++k) if (((i & (1 << k)) == 0)&&(isok[i | (1 << k)]))
- cost[k][i | (1 << k)] = min(cost[k][i | (1 << k)], cost[j][i] + map[j][k]);
- }
- }
- for (i = 1; i < (1 << n); ++i)
- if (i & 1) for (j = (i - 1) & i; j; j = (j - 1) & i)
- np[i] = min(np[i], np[j | 1] + np[(i - j) | 1]);
- return np[(1 << n) - 1];
- }
- int main() {
- int i;
- while (scanf("%d%d", &n, &m) != EOF) {
- Initial();
- for (i = 0; i < n; ++i)
- scanf("%d%d", &x[i], &y[i]);
- for (i = 0; i < n; ++i)
- scanf("%d", &val[i]);
- for (i = 1; i < (1 << n); ++i) {
- isok[i] = ok(i);
- if (isok[i]) state[tot++] = i;
- }
- ans1 = Solve_Tanxin();
- if (ans1 == INF)
- ans1 = ans2 = -1;
- else ans2 = TSP_Second();
- printf("%d %d\n", ans1, ans2);
- }
- return 0;
- }
1、基本概念:
(1)定义
欧拉通路 (欧拉迹)—通过图中每条边一次且仅一次,并且过每一顶点的通路。
欧拉回路 (欧拉闭迹)—通过图中每条边一次且仅一次,并且过每一顶点的回路。
欧拉图—存在欧拉回路的图。欧拉图就是从一顶出发每条边恰通过一次又能回到出发顶点的那种图,即不重复的行遍所有的边再回到出发点。
通路和回路-称vie1e2…envj为一条从 vi到 vj且长度为n的通路,其中长度是指通路中边的条数.称起点和终点相同的通路为一条回路。
简单图-不含平行边和自回路的图。
混合图-既有有向边,也有无向边的图
平凡图-仅有一个结点的图
完全图-有n个结点的且每对结点都有边相连的无向简单图,称为无向完全图;有n个结点的且每对结点之间都有两条方向相反的边相连的有向简单图为有向完全图。
(2)欧拉图的特征:
无向图
a)G有欧拉通路的充分必要条件为:G 连通,G中只有两个奇度顶点(它们分别是欧拉通路的两个端点)。
b)G有欧拉回路(G为欧拉图):G连通,G中均为偶度顶点。
有向图
a)D有欧拉通路:D连通,除两个顶点外,其余顶点的入度均等于出度,这两个特殊的顶点中,一个顶点的入度比出度大1,另一个顶点的入度比出度小1。
b)D有欧拉回路(D为欧拉图):D连通,D中所有顶点的入度等于出度。一个有向图是欧拉图,当且仅当该图所有顶点度数都是0。
2、弗罗莱(Fleury)算法思想-解决欧拉回路
Fleury算法:
任取v0∈V(G),令P0=v0;
设Pi=v0e1v1e2…ei vi已经行遍,按下面方法从中选取ei+1:
(a)ei+1与vi相关联;
(b)除非无别的边可供行遍,否则ei+1不应该为Gi=G-{e1,e2, …, ei}中的桥(所谓桥是一条删除后使连通图不再连通的边);
(c)当(b)不能再进行时,算法停止。
可以证明,当算法停止时所得的简单回路Wm=v0e1v1e2….emvm(vm=v0)为G中的一条欧拉回路,复杂度为O(e*e)。
3、欧拉算法C语言描述
void DFS(Graph &G,SqStack &S,int x,int t)
{
k=0;//一个标志,来标记当前访问的节点是否还有邻接边可供访问
Push(S,x); //将本次遍历边所经由的点入栈
for(i=t;i<v;i++) //v是顶点数,e是边数
if(G[i][x]>0)
{
k=1;
G[i][x]=0; G[x][i]=0; //此边已访问,删除此边
DFS(G,S,i,0);//寻找下一条关联的边,本次找到的是与x关联的i,在
//下一层中将寻找与i关联的边
break;
}//if,for
if(k==0) //如果k=0,说明与当前顶点关联的边已穷尽
{
Pop(S);
GetTop(S,m);
G[x][m]=1;G[m][x]=1;//恢复在上一层中被删除的边
a=x+1;//如果可能的话,从当前节点的下一条关联边开始搜寻
if(StackLength(S)!=e)//继续搜寻,边还没有全部遍历完
{
Pop(S); //还原到上一步去
DFS(G,S,m,a);//
}//if
else //搜寻完毕,将最后节点也入栈
Push(S,x);
}//if
}//DFS
void Euler(Graph &G,int x)
{
//G是存储图的邻接矩阵,都处理成无向图形式,值为1代表有边,0代表无边,不包括自回路,x是出发点
InitStack(S);//用来存放遍历边时依次走过的顶点
DFS(G,S,x,0);//深度优先遍历查找,0是指查询的起点
//输出
while(!StackEmpty(S))
{
GetTop(S,m);
printf("->v%d",m);
Pop(S);
}//while
}//Euler
如下为算法的图示动态过程:
4、欧拉算法的C实现
#include "SqStack.h" //堆栈的常见操作
#include "Queue.h"//队列的常见操作
typedef int Graph[200][200];
int v,e;
void DFS(Graph &G,SqStack &S,int x,int t)
{
int k=0,i,m,a;
Push(S,x);
for(i=t;i<v;i++)
if(G[i][x]>0)
{
k=1;
G[i][x]=0; //删除此边
G[x][i]=0;
DFS(G,S,i,0);
break;
}//if,for
if(k==0)
{
Pop(S);
GetTop(S,m);
G[x][m]=1;//恢复刚刚删除的边
G[m][x]=1;
a=x+1;//从下一条边开始搜寻
if(StackLength(S)!=e)
{
Pop(S);
DFS(G,S,m,a);
}//if
else
Push(S,x);
}//if
}//DFS
int BFSTest(Graph G)
{
int a[200],x,i,k=0;
LinkQueue Q;
InitQueue(Q);
EnQueue(Q,0);
for(i=0;i<v;i++)
a[i]=0;
a[0]=1;
while(!QueueEmpty(Q))
{
DeQueue(Q,x);
for(i=0;i<v;i++)
if(G[x][i]>0)
if(a[i]!=1)
{
a[i]=1;
EnQueue(Q,i);
}//if
}//while
for(i=0;i<v;i++)
if(a[i]==0)
{
k=1;
break;
}
if(k==1)
return 0;
else
return 1;
}
void Euler(Graph &G,int x)
{
int m;
SqStack S;
InitStack(S);
DFS(G,S,x,0);
printf("该图的一个欧拉回路为:");
while(!StackEmpty(S))
{
GetTop(S,m);
printf("->v%d",m);
Pop(S);
}//while
}
void InputM1(Graph &G)
{
int h,z;
printf("Please input 顶点数和边数/n");
scanf("%d",&v);
scanf("%d",&e);
for(int i=0;i<v;i++)
for(int j=0;j<v;j++)
G[i][j]=0;
printf("please int the 邻接矩阵的值(起点(数字) 终点(数字)):/n");
for(int i=0;i<e;i++)
{
scanf("%d",&h);
scanf("%d",&z);
G[h-1][z-1]=1;
G[z-1][h-1]=1;
}//for
}//InputM1
int main()
{
int i,j,sum,k=0;
Graph G;
InputM1(G);
if(BFSTest(G)==0)
{
printf("该图不是连通图!/n");
exit(0);
}//if
for(i=0;i<v;i++)
{
sum=0;
for(j=0;j<v;j++)
sum+=G[i][j];
if(sum%2==1)
{ k=1;
break;
}//if
}//for
if(k==1) printf("该图不存在欧拉回路!/n");
else
Euler(G,0); //从那个点出发
return 1;
}
顶点数5,边数为6
相关联的点1 2
1 3
2 5
4 2
3 2
4 5
运行结果(略)
5、小常识:欧拉算法的起由及一笔画问题
七桥问题:18世纪著名古典数学问题之一。在哥尼斯堡的一个公园里,有七座桥将普雷格尔河中两个岛及岛与河岸连接起来(如图)。问是否可能从这四块陆地中任一块出发,恰好通过每座桥一次,再回到起点?欧拉于1736年研究并解决了此问题,他把问题归结为如下右图的“一笔画”问题,证明上述走法是不可能的。
⒈凡是由偶点组成的连通图,一定可以一笔画成。画时可以把任一偶点为起点,最后一定能以这个点为终点画完此图。
⒉凡是只有两个奇点的连通图(其余都为偶点),一定可以一笔画成。画时必须把一个奇点为起点,另一个奇点终点。
⒊其他情况的图都不能一笔画出。(奇点数除以二便可算出此图需几笔画成。)
6、欧拉回路和Hamilton(汉密尔顿)回路
汉密尔顿图与欧拉图的区别只在于,边与顶点的区别,欧拉图是每边经过一次,汉密尔顿图是每顶经过一次。