yyda

找欧拉回路经典Hierholzer算法的简单理解(无严谨证明)

图论的基本知识:

图可以简单看成是很多点点连在一起。

每个点都是一个节点,对于这些节点:

每个节点相连的点数,也就是每个节点上相连的线的个数,被称为该节点的度。

图的类型

  • 无向图:节点对,也就是两个节点的连线是没有方向的,可以从A到B,也可以从B到A。

image

  • 有向图:和无向图相反,两个节点之间的连线是有方向的,只能指向一侧。

image

Hierholzer算法:欧拉回路中找欧拉路径

[!NOTE]

本篇目前对Hierholzer算法的解释和实现都在无向图中

了解欧拉回路

欧拉回路:

如果以一条路径走过一个图经过了所有的边且没有重复,那么就称这条路径为欧拉回路。简单点可以理解成这个图可以被一笔画成,毕竟一笔画不能倒回去。

存在欧拉回路的充分条件:如果这个图里所有的节点的度都是偶数,那么就一定存在欧拉回路,如果任何一个节点的度是奇数,那么不存在欧拉回路。以下是一个例子:

image

你可以选择任意陆地为起点,问你在每座桥必须而且只能通行一次的前提下能不能走过(到过即可,不限方向,)每一块陆地。

这是著名的戈尼斯堡七桥问题,答案是不可以,你可以通过前面欧拉回路的存在定理进行证明,但我们缺乏一个直观的通俗的解释。为了知道原理,让我们把这个问题抽象成如下图:

image

在这个图中D点的度数为3,也就是说D只有三条相连的路可以走。

现在思考:如果你想经过一个点(也即有进有出),而且不能走重复边,那么这个点最少要有两个可以走的相连的路:一个用来进入一个用来出去。同样不难想到如果一个点只有奇数个度,那么你最后一次进入这个点的时候会被困在里面,也就出不去了。如果是起点有奇数条边相连,那么最后一次出点的时候就没有路能回到起点了。

通过这种想象,我们可以感性地证明每一个节点的度数都必须是偶数才存在欧拉回路。

image
下图展示了一个简单的具有欧拉回路的图。你可以尝试画出路径,并感受一下为什么Hierholzer算法又被称为套圈法,这样用点连接的两个环路可以由任意欧拉回路路径抽象而出(随后证明)。
image

再考虑以下图,找从1出发的欧拉回路:

image

很容易我们就能找到两条路径:

image

这两条路径都可以重画为:

image

在这张图里有(2、4、5)(5、6、3)(1、2、3)三个圈((5,2,3)是被三个圈围成的),所以从这张图里可以比较直观的感受到找欧拉回路其实就是把所有的圈image
都找到,那么我们如何去找圈呢?

假设我们走的路线为1----2----3----1,还有两个圈没有被找到,而这些圈是在<1----2----3----1>沿途的,那么我们就倒回去发现在2处有一个圈,把路线改成<1----2----4----5----2----3----1>
同理回到3加上圈变成<1----2----4----5----2----3----5----6----3----1>,与我给出的第一条路线完全一致。

而这种回溯是通过DFS,也就是深度搜索实现的。这部分的实现全看个人对图问题coding能力和技巧,可以用pair建图,也可以使用链式前向星,对于最后的路线,用栈实现后再倒序输出即可,这里入栈时机考察对递归序的理解。

使用邻边表的代码实现如下:核心只有dfs函数

#include <bits/stdc++.h>
using namespace std;
#define MAXN 10005

int n, b[10], tot = 0;
vector <pair<int, int>> graph[MAXN];
bool vis[MAXN];
stack<int> res;

void add(int a, int b) {
    graph[a].emplace_back(b, tot);
    graph[b].emplace_back(a, tot++);
}

void dfs(int note) {
    for(auto &i : graph[note]) {
        if(vis[i.second]) {
            continue;
        }
        vis[i.second] = true;
        dfs(i.first);
    }
    res.push(note);
}

int main() {
    cin >> n;
    b[0] = 1;
    for(int i = 1; i <= n; i++) {
        b[i] = b[i - 1] + i;
    }
    for(int i = 0; i < n; i++) {
        for(int j = 0; j <= i; j++) {
            int p1 = b[i] + j, p2 = b[i + 1] + j, p3 = b[i + 1] + j + 1;
            add(p1, p2);
            add(p2, p3);
            add(p1, p3);
        }
    }
    dfs(1);
    cout << "Yes" << endl;
    while(!res.empty()) {
        cout << res.top() << " ";
        res.pop();
    }
    return 0;
}

posted on   yydawx  阅读(23)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App

导航

统计信息

点击右上角即可分享
微信分享提示