HDU5311、欧拉路径、Hierholzer算法以及其应用


本题可以转换为欧拉路径/欧拉回路做,转换可以有两种方法,求解欧拉路径/欧拉回路也有两种方法。

一种转换方式来自bilibili(题号为1003),通过成对连接所有度数为奇数的点,使其成为一个欧拉图,求得欧拉回路之后,将其中添加的边裁去即可。一种转换方式来自cnblog,每次连接到只剩一对度数为奇数的点,从这两点中求得欧拉路径,当然其中需要注意的是其裁切路径的方式,是使用添加边的索引,比较有技巧性,并且其使用了Hierholzer,如果没读明白会以为是普通的DFS,一开始我也觉得普通的DFS不会导致结果是树状结构而不是路径吗,但实际上DFS的visited标志并没有放在点上,而是放在了边上。

欧拉路径 Hierholzer算法(逐步插入回路法)

参考CSDN blog

figure1

由于CSDN上的图片好像不能引用,只能用mermaid画了一个比较丑的图。假设我们从A点进行DFS,得到路径A→B→C→D→E→B,此时因为B连接的所有边都被访问过了,所以这一波递归就到了头,此时递归向外一层(相当于出栈,同时刚才这一次递归的最后一条边进入最终确定路径中,即BE),回溯到了E。在E这里我们发现还有没走的路,于是我们沿着E→F→A→E又做了一次递归,于是像上述情况那样,我们边回溯边检查是否还有可走的边,回溯的过程中也会将当前路径进入最终确定路径中,所以从当E→A→F→E,这样回溯的时候,最终路径变成了BE EA AF FE,这时我们回到了刚才的E点,这次E点没有其他的边可以走了,因此继续回溯,最后得到欧拉路径BE EA AF FE ED DC CB BA。

这个方法好像就是在逐个找回路,并通过回溯,将回路末端到下一个回溯的入口(比如第一迭代的B→E)加入最后确定的路线中,因此叫做逐步插入回路法。需要注意的是,要保证算法的正确性,应该从欧拉路径的端点(即度数为奇数的点)开始遍历。比如下方的图形,如果像上图一样从A点递归,则可能出现A→B→C→D→E→B,然后E→F→A的情况。

figure2

虽然Fluery算法比较有名,但是复杂度没有该算法低。

代码

其实这个代码我没放到HDU上去提交

#include <cstdio>
#include <vector>
#include <cstring>
const int N = 100005;
struct Edge
{
    int to;
    int next;
    bool able;
} edge[N*4];

int n, m;
int degree[N];
int head[N];
int cnt, res;

bool visited[N];
std::vector<int> st;
std::vector<int> road[N];

void add(int u, int v)
{
    edge[++cnt].next = head[u];
    edge[cnt].to = v;
    edge[cnt].able = true;
    head[u] = cnt;
    ++degree[u];
}

inline void add_edge(int u, int v)
{
    add(u, v);
    add(v, u);
}

void dfs1(int s)
{
    visited[s] = true;
    if (degree[s] & 1)
        st.push_back(s);
    for (int i = head[s]; i; i = edge[i].next)
    {
        if (!visited[edge[i].to])
            dfs1(edge[i].to);
    }
}

void dfs2(int s)
{
    for (int i = head[s]; i; i = edge[i].next)
    {
        if (edge[i].able)
        {
            // printf("visit: %d %d\n", s, edge[i].to);
            edge[i].able = edge[i^1].able = false;
            dfs2(edge[i].to);
            if (i > 2*m + 1)
                ++res; // edge[i] is a edge that is added later, so this route should be cut here
            else
                road[res].push_back(i/2*(2*(i&1) - 1)); // find the original point
        }
    }
}

int main()
{
    int u, v;
    while (scanf("%d %d", &n, &m) != EOF)
    {
        std::memset(visited, 0, sizeof(visited));
        std::memset(head, 0, sizeof(head));
        std::memset(degree, 0, sizeof(head));
        cnt = 1;
        res = 0;
        for (int i = 0; i < m; ++i)
        {
            scanf("%d %d", &u, &v);
            add_edge(u, v);
        }
        for (int i = 1; i <= n; ++i)
        {
            // printf("loop: %d\n", i);
            if (!visited[i] && degree[i])
            {
                dfs1(i);
                if (st.empty())
                {
                    st.push_back(i);
                }
                for (int j = 2; j < st.size(); j+= 2)
                    add_edge(st[j], st[j+1]);
                dfs2(st[0]);
                ++res;
                st.clear();
            }
        }
        printf("%d\n", res);
        for (int i = 0; i < res; ++i)
        {
            printf("%d", road[i].size());
            for (int j = 0; j < road[i].size(); ++j)
                printf(" %d", road[i][j]);
            printf("\n");
            road[i].clear();
        }
    }
}

现实应用(mapbox)

知道这道题也是因为需要使用,问了ssy,刚好他说前几天做过这个题,所以我才知道这个算法。

当时的使用场景是,我使用Mapbox-GL-JS来做Cargo项目的网页可视化效果,由于我们有自己的路网,所以想将路网画在地图上,有例子是展示了画线,但是是geojson格式的折线,而我原格式的路网是以短线段形式(即两点之间),如果一条条短线来画的话我怕layer太多导致绘图负担增加(北京五环内路网有37w条边),所以想类似“几笔画”的问题,来讲部分线段连接起来,使之每一条线段都只被绘制一次,但是又一次可以绘制好几条线段。

最终得到的结果,37w+的北京路网需要一万多条折线来表示,曼哈顿路网需要800+,路网详见Cargo中的data/roadnetwork下的rnet文件。我做了一个视频,每一帧就添加一条折线,效果如下。Python代码后面补上。

figure3

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值