欧拉路径问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chent86/article/details/82819890

leetcode 332

Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], reconstruct the itinerary in order. All of the tickets belong to a man who departs from JFK. Thus, the itinerary must begin with JFK.

Note:

  1. If there are multiple valid itineraries, you should return the itinerary that has the smallest lexical order when read as a single string. For example, the itinerary ["JFK", "LGA"] has a smaller lexical order than ["JFK", "LGB"].
  2. All airports are represented by three capital letters (IATA code).
  3. You may assume all tickets form at least one valid itinerary.

题意很简单,抽象出来的话,就是从一个给定点出发,访问所有边且每条边只访问一次,也即欧拉路径问题。

直接的想法就是用DFS:当无路可走但是边还没有访问完时就“回退”,直到找到一条欧拉路径。想法很简单,但实现的过程中遇到诸多问题。

class Solution {
private:
    int edge_num = 0;
public:
    vector<string> findItinerary(vector<pair<string, string>> tickets) {
        this->edge_num = tickets.size();
        map<string,vector<string>> adj_list;
        for(const auto&i : tickets) {
            adj_list[i.first].push_back(i.second);  //提升效率
        }
        for(auto &i:adj_list)
            sort(i.second.begin(), i.second.end());
        vector<string> result = {"JFK"};
        string node = "JFK";
        int pass = 0;
        DFS(result, node, adj_list, pass);
        return result;
    }
    void DFS(vector<string>& result, string node, map<string,vector<string>>& adj_list, int& pass) {
        if(adj_list[node].size() == 0) {
            return;
        }
        int size = adj_list[node].size();
        for(auto i = adj_list[node].begin();i != adj_list[node].end();) {
            if(size == 0)
                break;
            size--;  //为了确定当前遍历的大小,如果DFS失败,edge会被重新添加回当前邻接链表中
            string new_node = *i;
            result.push_back(*i);  
            adj_list[node].erase(i);   //之后i将不可用,已经时后面的值了
            pass++;
            DFS(result, new_node, adj_list, pass);
            if(pass == this->edge_num)
                return;
            auto end = result.end();
            result.erase(--end);
            adj_list[node].push_back(new_node);
            pass--;
        }
    }
};

首先,创建邻接链表。

选择什么形式的STL容器比较合适?(由于STL容器用的不熟练,每次使用都要重复查询,之后得进行总结。)因为题目里有要求一定的顺序,那就使用map<string, set<string>>吧。但实际上,当访问失败要回退时,要将边重新加入map中,此时set又会进行一次排序,导致我们又再选择不正确的路径。所以不能用set,就换成vector吧,初始时进行排序就行了。

在创建过程中有一个技巧,按理来说,添加路径时,邻接链表的head应该先建立才能对它进行增添操作。所以就需要判断head是否已经创建。但实际上不用这样做,map已经帮我们做好了。(这一优化从beat 10%上升到beat 70%)

然后就是DFS递归部分。

返回条件:无边可选或者已经访问完所有的边。然后对当前结点的所有可选的路径进行处理,选择一条路径,将它从邻接链表删去,如果递归下去的结果访问完所有边,结束。如果没有,就不选择这条路径,并将它加回邻接链表(递归看着易懂,自己想的时候就不一定了)。需要注意的是,这个方法不好,在循环的过程中对正在循环的对象进行了增删操作,容易出bug。

我们再来看看其他人的优雅的代码,使用的是Hierholzer算法。

class Solution {
    unordered_map<string, priority_queue<string, vector<string>, greater<string>>> graph;
    vector<string> result;
    void dfs(string vtex) {
        auto & edges = graph[vtex];
        while (!edges.empty())
        {
            string to_vtex = edges.top();
            edges.pop();
            dfs(to_vtex);
        }
        result.push_back(vtex);
    }
public:
    vector<string> findItinerary(vector<pair<string, string>> tickets) {
        for (auto e : tickets)
            graph[e.first].push(e.second);
        dfs("JFK");
        reverse(result.begin(), result.end());
        return result;
    }
};

欧拉路径、回路的一些相关定理:

定理1:(a) 如果一个图G有超过两个奇数度的点,那么G没有欧拉路径

           (b) 如果G是连通的且有两个奇数度的点,那么G中存在一个欧拉路径。任何G中的欧拉路径都必须从一个奇数度点开始并结束于另一奇数度点。

定理2:如果图G是连通图且所有结点都有偶数的度,那么G中存在欧拉回路

题目给定起始点为“JFK”,所以终点要么是另一个奇数度的点,要么就是“JFK”。所以第一次“无边可走”时,到达了终点,最后一次“无边可走”时,到达了起点。将结果翻转之后就是答案。

猜你喜欢

转载自blog.csdn.net/chent86/article/details/82819890