拓扑排序与欧拉遍历

拓扑排序与欧拉遍历

拓扑排序

概念:
将一个有向图转化为一个线性序列的问题,且要求满足图中的顶点先后关系.
(即不与图相互冲突)
这里写图片描述

对于这个图来说,它的拓扑序可以为ABCD或ABDC,可见一个图的拓扑序并不是唯一的.但是无论怎样,如果说一个有向图存在一个拓扑序,前提是它不包含环.(A<B,B<C,C<A, 那是不可能转换为一个线性序列又与原图重合的)
我们也可以将一些数学关系理解为拓扑序.如A<B,B<C,那么就有了A<B<C.而如果是A<B,A<C,那么既有可能是A<B<C又有可能是A<C<B.

那么如何来求一个拓扑序呢?我们既可以采用DFS,又可以采用BFS.

BFS
我们可以每次取出入度为0的点从它开始找邻接点,并令所有邻接点的入度减一,从而转化为若干个子问题,放在队列中等待解决.

bool BFS()
{
    int d[MAXN+5];//记录入度
    queue<int> q;
    vector<int> ans;
    for(int i=1;i<=n;i++)
        if(d[i]==0)
            q.push(i);
    while(q.empty()==false)
    {
        int u=q.front();q.pop();
        bool FInd=false;
        for(node *p=Adj[u];p!=NULL;p=p->next)
        {
            int v=p->v;
            d[v]--;
            if(d[v]<=0)
            {
                ans.push(v);//最后的ans即为所求
                q.push_back(v);
            }
        }
    }
    if(ans.size()==n)
        return true;
    else
        return false;//存在环
}

时间复杂度:O(|N|+|E|)
DFS

对于这样一个图(E->C),我们尝试着进行一次DFS发现它的DFS的返回序列为HFCBGEDA,而它的一个拓扑序为ADEGBCFH,正好是前者的逆序.由此便可进行算法的实现了.

vector<int> ans;
int vis[MAXN+5];
bool DFS(int u)
{
    visu[u]=-1;//正在进行递归调用
    for(node *p=Adj[u];p!=NULL;p=p->next)
    {
        int v=p->v;
        if(vis[v]==-1)//找到了一个环
            return false;
        if(vis[v]==0&&dfs(v)==false)
            return false;
    }
    ans.push_back(u);
    vis[u]=1;
    return true;
}
bool TopSort()
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        if(vis[i]==0&&dfs(i)==false)
            return false;
    reverse(ans.begin(),ans.end());
    return true;
}

例题:士兵排队, sorting it all out, instruction arrangement.

欧拉遍历

欧拉遍历问题就是一笔画问题.如果在进行一次欧拉遍历后还能回到起点,那么就存在一条欧拉回路.
对于无向图,若所有的点的度数均为偶数,那么存在欧拉回路.若有且只有两个点的度数为奇数,那么存在一条欧拉路径,且入口和出口正好是这两个点.
对于有向图,若所有的点的出度均等于入度,则存在欧拉回路.若有且仅有两个点的入度不等于出度,且点A的出度减入度=1,点B的入度减出度=1,那么存在一条欧拉路径,且起点为点A,终点为点B.
那么如何解决欧拉路径问题呢?
我们采用的算法叫做消圈算法.不断遍历每一个以当前点为顶点的环,在找的过程中删去已经走过的边,最终在回溯的时候输出.
实现:

void AddEdge(int u,int v)
{
    node *p=++ncnt;
    p->to=v;
    p->vis=false;//用来删边
    p->next=Adj[u];
    Adj[u]=p;

    node *q=++ncnt;
    q->to=u;
    q->vis=false;
    q->next=Adj[v];
    Adj[v]=q;

    p->back=q,q->back=p;
}
void dfs(int u)
{
    for(node *p=Adj[u];p!=NULL;p=p->next)
        if(p->vis==false)
            p->back->vis=true,p->vis=true,dfs(p->to);
            //对于无向图来说,需要同时删去反向边
    printf("%d ",u);
}

我们知道,欧拉路径有可能不止一条,而有些题会要求我们输出字典序最小的一种,那么这种问题怎么解决呢?
我们可以这样来想:既然是在回溯的过程中输出,那么最先遍历的环必定最后输出,那么我们应该最先遍历顶点编号最小的点.
如果是用邻接矩阵来做的话,很好实现;但是如果使用邻接表来实现的话,我们需要考虑排序的问题.这里我们可以采用插入排序的思想,在每插入一条边的时候寻找到它适宜的位置,然后在邻接表中间进行插入操作.此时可以用vector或set简单实现,或者用手写邻接表的方式实现.

//手写邻接表的实现:
void AddEdge(int u,int v)
{
    node *p=++ncnt;
    p->v=v;
    node *l=Adj[u];
    node *r=r;
    while(r!=NULL&&r->v<=p->v)//或根据权值等需求自行调整
        l=r,r=r->next;
    if(l==r)
        p->next=Adj[u],Adj[u]=p;
    else
        l->next=p,p->next=r;
}

猜你喜欢

转载自blog.csdn.net/G20202502/article/details/79311817