欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hierholzer算法&重新安排行程

程序员文章站 2022-03-07 17:18:30
...

1、Hierholzer算法

欧拉迹是指一条包含图中所有边的一条路径,该路径中所有的边会且仅会出现一次。

一个无向图中包含欧拉迹,当且仅当下面两条性质同时满足:

  • 图是连通的
  • 图中每个顶点的度均为偶数

而一个有向图包含欧拉迹,当且仅当下面两条性质同时满足:

  • 图是连通的
  • 图中每个顶点入度和出度相同

Hierholzer算法用于在连通图寻找欧拉迹,其流程非常简单。

从一个可能的起点出发,进行深度优先搜索,但是每次沿着辅助边从某个顶点移动到另外一个顶点的时候,都需要删除这个辅助边。如果没有可移动的路径,则将所在结点加入到栈中,并返回。

void dfs(Node node, Deque trace){
	while(!node.edges.isEmpty())
	{
		Node next = node.edges.removeLast();
		dfs(next, trace);
	}
	trace.addLast(node);
}

最后得到的栈中保存的就是整个欧拉迹中的顶点。

这个算法应用起来非常简单,下面我们先证明几条性质。

性质1. 如果图中包含闭欧拉迹,则栈的底部存储的必定是起点。如果图中包含的是开欧拉轨迹,则栈底部存储的是与起点不同的另外一个奇度数端点。

证明:当我们要入栈时,说明当前所在顶点没有任何边了。考虑到从起点出发到当前结点的路径中,除了起点和当前顶点外,所有的顶点都失去了偶数度数(在移除了途经的边后)。如果起点和当前顶点不同,那么两者都失去了奇数度数。如果图中包含闭欧拉迹,这意味着所有顶点的初始度数都是偶数,而当前顶点的剩余度数为0,表示当前顶点的初始度数必定是奇数,当然这是不可能的,因此假设不成立,当前顶点就是起点。同样的对于开欧拉迹,当前顶点不可能是起点,否则起点的度数就是偶数,而开欧拉迹中起点和终点的度数一定是奇数。这样就能推出当前顶点既不是起点度数也是偶数,因此一定是终点。

性质2.如果图中包含闭欧拉迹,入栈的倒数第二个顶点一定是路径中的第二个顶点。

证明:由于路径中的第二个顶点入栈,说明起点已经入栈过,换言之,起点已经没有多余的边了。

参考原文:https://taodaling.github.io/blog/2019/04/25/Hierholzer%E7%AE%97%E6%B3%95/

2、例题:重新安排行程

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 出发。

说明:

如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
所有的机场都用三个大写字母表示(机场代码)。
假定所有机票至少存在一种合理的行程。
示例 1:

输入: [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
输出: ["JFK", "MUC", "LHR", "SFO", "SJC"]
示例 2:

输入: [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出: ["JFK","ATL","JFK","SFO","ATL","SFO"]
解释: 另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reconstruct-itinerary/

套用欧拉迹的公式,可以这样理解:

Hierholzer算法&重新安排行程

def findItinerary(self, tickets):
        import collections
        maps = collections.defaultdict(list)
        for start, end in sorted(tickets)[::-1]:
            maps[start].append(end)
            
        def dfs(cur, maps, res):
            while maps[cur]:
                dfs(maps[cur].pop(), maps, res)
            res.append(cur)
            return res[::-1]

        return dfs("JFK", maps, [])