转载请注明出处: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的结点编号放入
队列(此题放入优先队列中)中。 然后进行循环:
- 取出队头结点,视作边的起点。
- 然后“删除与该点相连的边”,代码就是将这个图中的该边另一个结点(即终点)的入度减一;
- 如果减一以后,终点的入度变为了0,那么将终点的编号入队列。
- 判断队列是否为空,若不空,则回到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;
}