拓扑排序(kahn算法)

1,定义

拓扑排序用于解决点之间的排序问题,如a点必须排在b点前面,c点必须排在d点后面,拓扑排序通过对这些关系建边来实现排序

2,思想

用度数表示一个点前面还有几个点排完序才轮到到,只有自己度数为0时才可以轮到他排序。

会注意到,当存在一个环时,这里面的点是不可能度数为0的,所以我们也可以利用这个来判断是否存在环

3,例题一,逃生

思路:

1,我们思考,要求序号小在前面,也就不一定是字典序最小。

eg:有边1->10,in[10]=0,in[2]=0,in[1]=1(即1需要10排完序才输出)。

如果按照字典序最小,排序是2,10,1

如果按照题目,排序是10,1,2(这样1更靠前)

2,正难则反,我们考虑,如果建立反向边,每次都把入度为0(in[i]==0)的点中序号最大的拿出来,那么较小的序号不久尽量靠前了吗

eg:还是上面那个,in[10]=1(即1),in[2]=0,in[1]=0

输出就是0,1,10(反过来输出就答案)

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;

//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 1e5 + 10;

int head[N], in[N], h[N], num;
struct node
{
	int next, to;
} edge[N];

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

int main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n, m, t, a, b;
	cin >> t;
	while (t--)
		{
			memset(head, 0, sizeof(head));
			num = 0;
			memset(in, 0, sizeof(in));
			int k = 0;
			cin >> n >> m;
			for (int i = 1; i <= m; ++i)
				{
					cin >> a >> b;
					add(b, a);
					in[a]++;
				}
			priority_queue<int>q;//优先队列把序号大的序号为0的先输出
			for (int i = 1; i <= n; ++i)if (!in[i])q.push(i);
			while (!q.empty())
				{
					int u = q.top();
					q.pop();
					h[++k] = u;
					for (int i = head[u]; i; i = edge[i].next)
						{
							int v = edge[i].to;
							if (--in[v] == 0)q.push(v);//入度为0就可以存入
						}
				}
			for (int i = k; i >= 1; --i)//反向输出
				{
					cout << h[i];
					if (i == 1)cout << endl;
					else cout << ' ';
				}
		}
	return 0;
}

例题二:Rank of Tetris

思路:

1,我们可以发现就是简单的跑个入度即可

2,当然,=号有些难操作,我们思考他的作用,假设a=b(a序号<b),而信息给了m1个严格大于a的数,又给了m2个严格小于b的数,实际隐含有m1个严格大于b,还有m2个严格小于a这样的信息,即我们应该把ab合并一起。合并使用并查集

3,我们考虑合并的影响,合并后我们需要注意是否这个“点”会形成自环(矛盾)。

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
		
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 2e4 + 10;
int head[N], in[N], num, a[N], b[N], n, m, fa[N], flag, ans;
char s[N];
struct node
{
	int next, to;
} edge[N];
		
void add(int u, int v)
{
	edge[++num].next = head[u];
	edge[num].to = v;
	head[u] = num;
}
		
void init()
{
	memset(head, 0, sizeof(head));
	memset(in, 0, sizeof(in));
	for (int i = 0; i < n; ++i)fa[i] = i;//初始父亲为自己
	num = 0;
	flag = 1;//一开始flag为可以,后面0则不确定,-1则不行
	ans = n;//ans表示合并后剩余点的数量
}
		
int find(int x)
{
	if (fa[x] != x)fa[x] = find(fa[x]);
	return fa[x];
}
		
void mjudge()
{
	queue<int>q;
	int cnt = 0;
	for (int i = 0; i < n; ++i)if (!in[i] && i == fa[i])q.push(i);//首先该点入度为0而且还要是自己就是祖先才存入(所有剩余的点都是祖先,不是祖先的说明被合并了)
	while (!q.empty())
		{
			if ((int)q.size() > 1)flag = 0;//如果同时入度02个以上,说明不明确输出状况了,flag为0,但是不能直接返回,因为矛盾优先度高,必须跑完才知道是否矛盾
			int u = q.front();
			q.pop();
			cnt++;
			for (int i = head[u]; i; i = edge[i].next)
				{
					int v = edge[i].to;
					if (v == u)
						{
							flag = -1;//矛盾直接返回
							return;
						}
					if (--in[v] == 0)q.push(v);
				}
		}
	if (cnt < ans)flag = -1;//最后不是所有剩余点都入度,那么存在环
}
		
int main()
{
	int x, y;
	while (cin >> n >> m)
		{
			init();//该处初始化
			for (int i = 1; i <= m; ++i)
				{
					cin >> a[i] >> s[i] >> b[i];
					if (s[i] == '=')//先合并操作
						{
							x = find(a[i]), y = find(b[i]);
							if (x != y)fa[x] = y, ans--;//即使数据给重复,我们也不会重复合并,每一次合并都会减少一个点,即使是两个合并点合并成一个点也一样,每次合并,剩余点ans减一
						}
				}
			for (int i = 1; i <= m; ++i)if (s[i] != '=')//再对合并完的点操作
					{
						x = find(a[i]), y = find(b[i]);
						if (s[i] == '>')add(x, y), in[y]++;
						else add(y, x), in[x]++;
					}
			mjudge();
			if (flag == 1)cout << "OK" << endl;
			else if (!flag)cout << "UNCERTAIN" << endl;
			else cout << "CONFLICT" << endl;
		}
		
	return 0;
}

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值