Unverified Commit 6bbe1533 authored by Shuhao Zhang's avatar Shuhao Zhang Committed by GitHub
Browse files

Merge pull request #2645 from NachtgeistW/euler

refactor[euler.md]: 欧拉图
parents 11e1a5ef 7a738b3b
Loading
Loading
Loading
Loading
+72 −60
Original line number Diff line number Diff line
本页面将简要介绍欧拉图的概念、实现和应用。

## 定义

通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。
@@ -36,24 +38,17 @@

## 求欧拉回路或欧拉路

例题: [Luogu P2731 骑马修栅栏](https://www.luogu.com.cn/problem/P2731) 

???+note "题面"
    给定一张有 500 个顶点的无向图,求这张图的一条欧拉路或欧拉回路。如果有多组解,输出最小的那一组。
    
    在本题中,欧拉路或欧拉回路不需要经过所有顶点。
    
    边的数量 m 满足 $1\leq m \leq 1024$ 。

### Fleury 算法

也称避桥法,一个偏暴力的算法。算法流程为每次选择下一条边的时候优先选择不是桥的边。
也称避桥法,是一个偏暴力的算法。

算法流程为每次选择下一条边的时候优先选择不是桥的边。

一个广泛使用但是错误的实现方式是先 Tarjan 预处理桥边,然后再 DFS 避免走桥。但是由于走图过程中边会被删去,一些非桥边会变为桥边导致错误。最简单的实现方法是每次删除一条边之后暴力跑一遍 Tarjan 找桥,时间复杂度是 $\Theta(m(n+m))=\Theta(m^2)$ 。复杂的实现方法要用到动态图等,实用价值不高。

在解决本题的时候只需要再贪心就好,不过由于复杂度不对,还是看更优的算法吧。
### Hierholzer 算法

### 逐步插入回路法(Hierholzer 算法)
也称逐步插入回路法

算法流程为从一条回路开始,每次任取一条目前回路中的点,将其替换为一条简单回路,以此寻找到一条欧拉回路。如果从路开始的话,就可以寻找到一条欧拉路。

@@ -77,19 +72,64 @@ $$
\end{array}
$$

这个算法的时间复杂度约为 $O(nm+m^2)$ 。实际上还有复杂度更低的实现方法,就是将找回路的 Dfs 和 Hierholzer 的递归合并,边找回路边 Hierholzer,保存答案可以使用 `stack<int>` ,因为如果找的不是回路的话必须将那一部分放在最后
这个算法的时间复杂度约为 $O(nm+m^2)$ 。实际上还有复杂度更低的实现方法,就是将找回路的 DFS 和 Hierholzer 算法的递归合并,边找回路边使用 Hierholzer 算法

如果需要输出字典序最小的欧拉路或欧拉回路的话,因为需要将边排序,时间复杂度是 $\Theta(n+m\log m)$ (计数排序或者基数排序可以优化至 $\Theta(n+m)$ )。如果不需要排序,时间复杂度是 $\Theta(n+m)$ 。

对于这道例题的一个代码实现如下。注意,不能使用邻接矩阵存图,否则时间复杂度会退化为 $\Theta(nm)$ 。由于需要将边排序,建议使用前向星或者 vector 存图。示例代码使用 vector。
### 应用

有向欧拉图可用于计算机译码。

设有 $m$ 个字母,希望构造一个有 $m^n$ 个扇形的圆盘,每个圆盘上放一个字母,使得圆盘上每连续 $n$ 位对应长为 $n$ 的符号串。转动一周( $m^n$ 次)后得到由 $m$ 个字母产生的长度为 $n$ 的 $m^n$ 个各不相同的符号串。

![](images/euler1.png)

构造如下有向欧拉图:

设 $S = \{a_1, a_2, \cdots, a_m\}$ ,构造 $D=<V, E>$ ,如下:

???+note "代码实现"
 $V = \{a_{i_1}a_{i_2}\cdots a_{i_{n-1}} |a_i \in S, 1 \leq i \leq n - 1 \}$ 

 $E = \{a_{j_1}a_{j_2}\cdots a_{j_{n-1}}|a_j \in S, 1 \leq j \leq n\}$ 

规定 $D$ 中顶点与边的关联关系如下:

顶点 $a_{i_1}a_{i_2}\cdots a_{i_{n-1}}$ 引出 $m$ 条边: $a_{i_1}a_{i_2}\cdots a_{i_{n-1}}a_r, r=1, 2, \cdots, m$ 。

边 $a_{j_1}a_{j_2}\cdots a_{j_{n-1}}$ 引入顶点 $a_{j_2}a_{j_3}\cdots a_{j_{n}}$ 。

![](images/euler2.png)

这样的 $D$ 是连通的,且每个顶点入度等于出度(均等于 $m$ ),所以 $D$ 是有向欧拉图。

任求 $D$ 中一条欧拉回路 $C$ ,取 $C$ 中各边的最后一个字母,按各边在 $C$ 中的顺序排成圆形放在圆盘上即可。

## 例题

???+note "[洛谷 P2731 骑马修栅栏](https://www.luogu.com.cn/problem/P2731)"

    给定一张有 500 个顶点的无向图,求这张图的一条欧拉路或欧拉回路。如果有多组解,输出最小的那一组。

    在本题中,欧拉路或欧拉回路不需要经过所有顶点。

    边的数量 m 满足 $1\leq m \leq 1024$ 。

??? note "解题思路"

    用 Fleury 算法解决本题的时候只需要再贪心就好,不过由于复杂度不对,还是换 Hierholzer 算法吧。

    保存答案可以使用 `stack<int>` ,因为如果找的不是回路的话必须将那一部分放在最后。

    注意,不能使用邻接矩阵存图,否则时间复杂度会退化为 $\Theta(nm)$ 。由于需要将边排序,建议使用前向星或者 vector 存图。示例代码使用 vector。

??? note "示例代码"
    ```cpp
    #include <algorithm>
    #include <cstdio>
    #include <stack>
    #include <vector>
    using namespace std;
    ```

    struct edge {
      int to;
@@ -169,36 +209,8 @@ $$
    }
    ```

### 应用

有向欧拉图可用于计算机译码。

设有 $m$ 个字母,希望构造一个有 $m^n$ 个扇形的圆盘,每个圆盘上放一个字母,使得圆盘上每连续 $n$ 位对应长为 $n$ 的符号串。转动一周( $m^n$ 次)后得到由 $m$ 个字母产生的长度为 $n$ 的 $m^n$ 个各不相同的符号串。

![](../images/euler1.png)

构造如下有向欧拉图:

设 $S = \{a_1, a_2, \cdots, a_m\}$ ,构造 $D=<V, E>$ ,如下:

 $V = \{a_{i_1}a_{i_2}\cdots a_{i_{n-1}} |a_i \in S, 1 \leq i \leq n - 1 \}$ 

 $E = \{a_{j_1}a_{j_2}\cdots a_{j_{n-1}}|a_j \in S, 1 \leq j \leq n\}$ 

规定 $D$ 中顶点与边的关联关系如下:

顶点 $a_{i_1}a_{i_2}\cdots a_{i_{n-1}}$ 引出 $m$ 条边: $a_{i_1}a_{i_2}\cdots a_{i_{n-1}}a_r, r=1, 2, \cdots, m$ 。

边 $a_{j_1}a_{j_2}\cdots a_{j_{n-1}}$ 引入顶点 $a_{j_2}a_{j_3}\cdots a_{j_{n}}$ 。

![](../images/euler2.png)

这样的 $D$ 是连通的,且每个顶点入度等于出度(均等于 $m$ ),所以 $D$ 是有向欧拉图。

任求 $D$ 中一条欧拉回路 $C$ ,取 $C$ 中各边的最后一个字母,按各边在 $C$ 中的顺序排成圆形放在圆盘上即可。

## 练习题
## 习题

 [洛谷 1341无序字母对](https://www.luogu.com.cn/problem/P1341) 
-  [洛谷 P1341 无序字母对](https://www.luogu.com.cn/problem/P1341) 

 [洛谷 2731骑马修栅栏](https://www.luogu.com.cn/problem/P2731) 
-  [洛谷 P2731 骑马修栅栏](https://www.luogu.com.cn/problem/P2731) 
(5.84 KiB)

File moved.

(7.42 KiB)

File moved.