1. 程式人生 > >拓撲排序((算法競賽入門經典)劉汝佳)

拓撲排序((算法競賽入門經典)劉汝佳)

沒有 -1 nts adjacency lag 過大 content tail popu

轉載請註明出處:http://blog.csdn.net/u012860063?

viewmode=contents



【分析】(小白)

把每一個變量看成一個點,“小於”關系看成有向邊,則我們得到了一個有向圖。這樣,我們的任務實際上是把一個圖的全部結點排序,使得每一條有向邊(u,v)相應的u都排在v的前面。在圖論中,這個問題稱為拓撲排序。

不難發現:假設圖中存在有向環,則不存在拓撲排序,反之則存在。我們把不包括有向環的有向圖稱為有向無環圖。能夠借助dfs函數完畢拓撲排序:在訪問完一個結點之後把它加到當前拓撲排序的首部。


一、輸出隨意一種滿足優先條件的就可以:

代碼例如以下:

#include <cstdio>
#include <cstring>
#define MAXN 517
int G[MAXN][MAXN];
int c[MAXN],topo[MAXN];
int t;
int n, m;
bool dfs(int u)
{
	c[u] = -1;
	for(int v = 1; v <= n; v++)
	{
		if(G[u][v])
		{
			if(c[v] < 0)
				return false;
			else if(!c[v] && !dfs(v))
				return false;
		}
	}
	c[u] = 1;
	topo[--t] = u;
	return true;
}
bool toposort()
{
	t = n;
	memset(c,0,sizeof(c));
	for(int u = 1; u <= n; u++)
	{
		if(!c[u])
		{
			if(!dfs(u))
				return false;
		}
	}
	return true;
}
int main()
{
	int a, b;
	int i;
	while(~scanf("%d%d",&n,&m))
	{
		for(i = 0; i < m; i++)
		{
			scanf("%d%d",&a,&b);
			G[a][b] = 1;
		}
		if(toposort())
		{
			bool ok = true;
			for(i = 0; i < n; i++)
			{
				if(ok)
				{
					printf("%d",topo[i]);
					ok = false;
				}
				else
					printf(" %d",topo[i]);
			}
		}
		printf("\n");
	}
	return 0;
}


這裏用到了一個c數組,c[u]=0表示從來沒有訪問過(從來沒有調用過dfs(u));c[u]=1表示已經訪問過。而且還遞歸訪問過它的全部子孫(即dfs(u)曾被調用過。並已返回)。c[u]=-1表示正在訪問(即遞歸調用dfs(u)正在棧幀中,尚未返回)。



二、輸出滿足條件且要求輸出時字典序小的在前:

此時須要用到優先隊列

拓撲排序

首先統計每一個結點的入度。將度為0的結點編號放入隊列(此題放入優先隊列中)中。 然後進行循環:
  1. 取出隊頭結點,視作邊的起點。
  2. 然後“刪除與該點相連的邊”,代碼就是將這個圖中的該邊還有一個結點(即終點)的入度減一。
  3. 假設減一以後,終點的入度變為了0,那麽將終點的編號入隊列
  4. 推斷隊列是否為空。若不空,則回到1

優先隊列

C++ STL中有優先隊列的類——priority_queue<T>。默認優先隊列是值越大,優先級越高。所以比方priority_queue<int> q。

這裏面的元素是降序排列的。

假設我們要實現升序須要重載。

priority_queue<int,vector<int>,greater<int> > q;

代碼例如以下:(HDU1285)

#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std;
bool map[517][517];
int in[517];
priority_queue<int,vector<int>,greater<int> > q;
void toposort(int n)
{
    for(int i=1;i<=n;i++)
    {
        if(in[i]==0)//入度為0的放入隊列中
            q.push(i);
    }
    int c=1;
    while(!q.empty())
    {
        int v=q.top();
        q.pop();
        if(c!=n)
        {
            cout<<v<<" ";
            c++;
        }
        else
            cout<<v<<endl;
        for(int i=1;i<=n;i++)
        {
            if(!map[v][i])
                continue;
            in[i]--;
            if(!in[i])//入度為0的放入隊列
                q.push(i);

        }
    }
}
int main()
{
    int n,m,i,j;
    while(cin>>n>>m)
    {
        int k=0;
        memset(map,0,sizeof map);
        memset(in,0,sizeof in);
        while(m--)
        {
            cin>>i>>j;
            if(map[i][j])//去重
                continue;
            map[i][j]=1;
            in[j]++;
        }
        toposort(n);
    }
}



三、直接每次先輸出入度為零的點(HDU1285):http://acm.hdu.edu.cn/showproblem.php?pid=1285

和二是一樣的思路,僅僅是沒有使用優先隊列。而是使用了數組;

代碼例如以下:

#include <cstdio>
#include <cstring>
#define MAXN 517
int G[MAXN][MAXN];//路徑
int in_degree[MAXN];//入度
int ans[MAXN];
int n, m, x, y;
int i, j;

void toposort()
{
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= n; j++)
		{
			if(G[i][j])
			{
				in_degree[j]++;
			}
		}
	}
	for(i = 1; i <= n; i++)//從最小的開始尋找,
	{//這樣保證了有多個答案時序號小的先輸出
		int k = 1;
		while(in_degree[k] != 0)//尋找入度為零的點
			k++;
		ans[i] = k;
		in_degree[k] = -1;
		//更新為-1,後邊檢測不受影響,相當於刪除節點
		for(int j = 1; j <= n; j++)
		{
			if(G[k][j])
				in_degree[j]--;//相關聯的入度減1
		}
	}
}

void init()
{
	memset(in_degree,0,sizeof(in_degree));
	memset(ans,0,sizeof(ans));
	memset(G,0,sizeof(G));
}

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		init();
		for(i = 0; i < m; i++)
		{
			scanf("%d%d",&x,&y);
			G[x][y] = 1;
		}
		toposort();
		for(i = 1; i < n; i++)
			printf("%d ",ans[i]);
		printf("%d\n",ans[n]);
	}
	return 0;
}

以上解說的都是二維的用的是鄰接矩陣存圖,僅僅適用於稠密圖,數據過大就不能存了。


以下是用鄰接表存圖的拓撲排序, 適用於大數據。稀疏圖!

一、數組式鄰接表:

代碼例如以下:

#include <iostream>
using namespace std;
int ind[517];       // indegree入度個數
int adj[250017];    //adjacency list鄰接表位置值
int adj_next[250017];//鄰接表下一指針
int tail[517];      //鄰接表尾

int main()
{
    int n,m,i,j,a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i = 0; i <= n; i++) 
		{ 
            tail[i] = -1;
            adj[i] = -1;
            adj_next[i] = -1;
            ind[i] = 0;
        }
        for(i = 0; i < m; ++i)
        {
            scanf("%d%d",&a,&b);
            int x = tail[a],flag = 0;
            while(x != -1)  //推斷是否重邊
            {
                if(adj[x] == b)
				{
                    flag = 1;
                    break;
                }
                x = adj_next[x];
            }
            if(!flag)//關聯a的鄰接表
            {
                adj[i] = b;
                adj_next[i] = tail[a];
                tail[a] = i;
                ind[b] ++;
            }
        }
        for(i = 1;i <= n; i++)//找n次
        {
            for(j = 1;j <= n;j++)//遍歷
            {
                if(ind[j] == 0)//當入度為0時。說明靠前
				{
					ind[j] = -1;//在下次尋找入度為0時跳過
                    if(i == 1)  
						printf("%d",j);
                    else       
						printf(" %d",j);
                    for(int k = tail[j]; k != -1; k = adj_next[k])//鄰接位置入度減一
                    {
                        ind[adj[k]]--;
                    }
                    break;
                }
            }
        }
        printf("\n");
    }
    return 0;
}


二、結構體(鏈表)式鄰接表:

代碼例如以下:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<cstdio>
#include<queue>
#include<bitset>
using namespace std;
#define maxn 505

struct node
{
    int num;
    node *next;
};

node map[maxn];
int d[maxn], n;

void Insert(int a, int b);
void Free();
void Topsort();

int main()
{
    int m;

    while(cin >> n >> m)
    {
        int i, a, b;

        memset(d, 0, sizeof(d));

        for(i=0; i<m; i++)
        {
            cin >> a >> b;
            Insert(a, b);
            d[b]++;
        }

        Topsort();
        Free();
    }

    return 0;
}
void Insert(int a, int b)
{
    node *newnode;

    newnode = (node *)malloc(sizeof(node));
    newnode->num = b;
    newnode->next = map[a].next;
    map[a].next = newnode;
}
void Free()
{
    node *cur, *old;

    for(int i=1; i<=n; i++)
    {
        cur = map[i].next;
        while(cur)
        {
            old = cur;
            cur = cur->next;
            free(old);
        }
        map[i].next = NULL;
    }
}
void Topsort()
{
    priority_queue<int, vector<int>, greater<int> > que;
    int nx, i;
    int q[maxn]={0}, k=0;
    node *cur;

    for(i=1; i<=n; i++)
    {
        if(d[i] == 0)
            que.push(i);
    }

    while(que.size())
    {
        nx = que.top(), que.pop();
        q[k++] = nx;

        cur = map[nx].next;
        while(cur)
        {
            nx = cur->num;
            d[nx] -= 1;

            if(d[nx] == 0)
                que.push(nx);
            cur = cur->next;
        }
    }

    cout << q[0];
    for(i=1; i<k; i++)
        cout <<" "<< q[i];
    cout <<endl;
}


還有一種(較特殊):

逆排序+優先隊列;(HDU4587):http://acm.hdu.edu.cn/showproblem.php?pid=4857

也就是HDU第一次BC的第一題!

要使小的序號盡量靠前。小的序號盡量靠前並非代表字典序。要求多種情況時,先使1靠前(可能1僅僅能在第2或第3位 那麽就要使它在第2位),其次2。3。。而不是在當前情況下,該位最小是哪個就輸出哪個;

代碼例如以下:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
#define N 30017
int n, m;
int i, j, k;
int v[N],ans[N];
vector<int>P[N];
void init()
{
	memset(ans,0,sizeof(ans));
	memset(v,0,sizeof(v));
}
void Topsort()
{
	priority_queue<int>Q;
	int size, t;
	for(i = 1; i <= n; i++)
	{
		if(v[i] == 0)
			Q.push(i);
	}
	while(!Q.empty())
	{
		t = Q.top();
		Q.pop();
		size = P[t].size();
		for(i = 0; i < size; i++)//相關聯的入度減1
		{
			v[P[t][i]]--;
			if(v[P[t][i]] == 0)
				Q.push(P[t][i]);
		}
		ans[k++] = t;
	}
}
int main()
{
	int T;
	int a, b;
	scanf("%d",&T);
	while(T--)
	{
		init();
		scanf("%d%d",&n,&m);
		for(i = 1; i <= n; i++)//清空
		{
			P[i].clear();
		}
		for(i = 0; i < m; i++)
		{
			scanf("%d%d",&a,&b);
			v[a]++;
			P[b].push_back(a);//逆向建圖,在b後面加入a
		}
		k = 0;
		Topsort();
		for(i = n-1; i > 0; i--)//逆向輸出
		{
			printf("%d ",ans[i]);
		}
		printf("%d\n",ans[0]);
	}
	return 0;
}



拓撲排序((算法競賽入門經典)劉汝佳)