「学习笔记」哈密尔顿通路&哈密尔顿回路

感想

在有限的 OIer 生涯中应该再也不会去研究这种东西了。在 OI 界太冷门了。

艹,清北学堂国庆的Day4T4好像和哈密尔顿通路有关,我是毒奶?

概念

哈密尔顿通路:图 \(G\) 中一条从 \(S\)\(T\) 的路径不重不漏地经过了每个点,那么这条路径称为哈密尔顿通路

哈密尔顿回路:图 \(G\) 中一条从 \(S\)\(S\) 的路径不重不漏地经过了除 \(S\) 外每个点并且 \(S\) 只经过过两次,那么这条路径称为哈密尔顿回路

哈密尔顿图:若图 \(G\)存在哈密尔顿回路则称 \(G\) 为哈密尔顿图。

\(N\) 阶竞赛图:含有 \(N\) 个顶点的有向图,且每对顶点之间都有一条边(不限定方向)。

无向简单图:不含重边和自环的无向图。

求解方法

求解哈密尔顿通路

充分条件:
设 G 是有 \(n\) 个点的无向简单图(\(n \geq 2\)),若 \(G\) 中任意不相邻的两点 \(u,v\) 都满足 \(d(u) + d(v) \geq n - 1\),则 \(G\) 有哈密尔顿通路。
是下面涉及到的Ore定理的推论。

可以转化成求哈密尔顿回路:

枚举哈密尔顿通路的起点 \(S\) 和终点 \(T\),在 \(S\)\(T\) 之间加一条边,判断是否存在哈密尔顿通路。

\(N(N\geq2)\)阶竞赛图中一定存在哈密尔顿通路
证明:
很显然 \(N = 2\) 时成立(自己画图看看就行)。
\(N = k\) 时是成立的,去证明 \(N = k + 1\) 时是成立的。
\(N = k\) 时存在的哈密尔顿通路为 \(v_1,v_2,\dots,v_k\)。(下标只是代表在走的时候经过的顺序)
\(i = k \sim 1\) 去看 \(v_i\)\(v_{k+1}\) 的联通情况。
若存在 \(v_i\) 指向 \(v_{k+1}\) 的边,那么存在 \(v_1,\dots,v_i,v_{k+1},v_{i+1},\dots,v_k\) 这样一条通路。
否则存在 \(v_{k+1},v_1,v_2,\dots,v_k\) 这样一条通路。
证毕。

在竞赛图中找哈密尔顿通路的代码(就是模拟上述过程):

//感觉时间复杂度是 O(n ^ 3)
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 6180

int n, m, now, ans[M];
bool p[M][M];

void ins(int pos, int x) {
    int temp;
    ++now;
    for (int i = pos; i <= now; ++i) {
        temp = ans[i], ans[i] = x, x = temp;
    }
}

void hamilton() {
    now = 2, ans[1] = 1;
    for (int i = 2; i <= n; ++i) {
        bool okay = false;
        for (int j = now - 1; j >= 1; --j) {
            if (p[ans[j]][i]) {
                okay = true;
                ins(j + 1, i);
                break;
            }
        }
        if (!okay) ins(1, i);
    }
}

int main() {
    scanf("%d", &n);
    m = n * (n - 1) / 2;
    for (int i = 1, u, v; i <= m; ++i) {
        scanf("%d %d", &u, &v);
        if (u < v) p[u][v] = 1;
    }
    hamilton();
    for (int i = 1; i <= n; ++i) printf("%d\n",ans[i]);
    return 0;
}

求解哈密尔顿回路

是个NP-完全问题,好像没什么好的求解方法,都是根据一些充分条件/必要条件去构造的鸭子。

必要条件:
\(G\{V,E\}\) 是哈密尔顿图。
那么对于 \(V\) 的任意非空子集 \(V'\) 都有:\(P(G-V') \leq |V'|)\)(其中 \(P(G)\) 表示图 \(G\) 的连通分量个数,\(|V'|\) 表示 \(V'\) 中元素个数,\(G-V'\) 表示从 \(G\) 中删掉 \(V'\) 中的元素后得到的图)。

证明:
\(G\) 中的哈密尔顿回路为 \(C\),那么 \(P(G-V')\leq P(C-V')\)。因为往 \(C-V'\) 这个图中加边连通分量的数量是不会改变的。
只需要证明 \(P(C-V') \leq |V'|\) 即可。
\(C\) 一定是个环,开始删点,删掉的第一个点使得 \(C\) 不再是一个环,之后每次删点最多增加一个连通分量,也就是最多有 \(|V'|\) 个连通分量。
证毕。

这只是个必要条件,下图满足这个条件但不是哈密尔顿图。

必要条件的推论:
\(G\{V,E\}\) 中有哈密尔顿通路。那么对于 \(V\) 的任意非空子集 \(V'\) 都有:\(P(G-V') \leq |V'| + 1)\)
证明类似于上面那个证明,不再重复证了。

几个示例:

以下是充分条件
Dirac 定理:
设 G 是有 \(n\) 个点的无向简单图(\(n \geq 3\)),若 \(G\) 中每个节点的度数至少为 \(\left\lceil\frac{n}{2}\right\rceil\),则 \(G\) 为哈密尔顿图。
Ore 定理:
设 G 是有 \(n\) 个点的无向简单图(\(n \geq 3\)),若 \(G\) 中任意不相邻的两点 \(u,v\) 都满足 \(d(u) + d(v) \geq n\),则 \(G\) 为哈密尔顿图。
证明鸽了。

在满足 Dirac 定理的情况下找哈密尔顿回路(不保证正确)。

//复杂度好像是 O(n ^ 2)
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 1001

int n, m, now, d[M], ans[M];
bool map[M][M], used[M];

void reverse(int l, int r) {
    int temp;
    while (l < r) {
        temp = ans[l];
        ans[l] = ans[r];
        ans[r] = temp;
        ++l, --r;
    }
}

void hamilton() {
    int s, t;
    s = ans[1] = 1, now = 2, used[1] = true;
    for (int i = 2; i <= n; ++i) {
        if (map[1][i]) {
            t = ans[2] = i;
            used[i] = true;
            break;
        }
    }
    while (true) {
        while (true) {
            bool okay = false;
            for (int i = 1; i <= n; ++i) {
                if (used[i]) continue;
                if (map[t][i]) {
                    ans[++now] = i;
                    okay = true;
                    used[i] = true;
                    t = i; break;
                }
            }
            if (!okay) break;
        }
        reverse(1, now);
        s = ans[1], t = ans[now];
        while (true) {
            bool okay = false;
            for (int i = 1; i <= n; ++i) {
                if (used[i]) continue;
                if (map[t][i]) {
                    ans[++now] = i;
                    okay = true;
                    used[i] = true;
                    t = i; break;
                }
            }
            if (!okay) break;
        }
        if (!map[s][t]) {
            int flag = -1;
            for (int i = 2; i <= now - 2; ++i) {
                if (map[t][ans[i]] && map[s][ans[i + 1]]) {
                    flag = i; break;
                }
            }
            t = ans[flag + 1];
            reverse(flag + 1, now);
        }
        if (now == n) break;
        int flag1 = -1, flag2 = -1;
        for (int i = 1; i <= n; ++i) {
            flag1 = i;
            if (used[i]) continue;
            for (int j = 1; j <= now - 2; ++j) {
                if (map[ans[j]][i]) {
                    flag2 = j;
                    break;
                }
            }
            if (flag2 != -1) break;
        }
        s = ans[flag2 - 1], t = flag1;
        reverse(1, flag2), reverse(flag2 + 1, now);
        ans[++now] = flag1;
        used[flag1] = true;
    }
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1, u, v; i <= n; ++i) {
        scanf("%d %d", &u, &v);
        ++d[u], ++d[v];
        map[u][v] = map[v][u] = true;
    }
    for (int i = 1; i <= n; ++i) {
        if (d[i] < ceil(1.0 * n / 2.0)) {
            puts("This does not conform to Dirac's theorem");
            return 0;
        }
    }
    hamilton();
    for (int i = 1; i <= n; ++i) printf("%d ", ans[i]);
    return 0;
}

To Be Continued

参考资料

posted @ 2020-09-27 15:57  yu__xuan  阅读(4587)  评论(8编辑  收藏  举报