CSP-拓扑排序和Kahn算法

CSP-拓扑排序和Kahn算法

基础知识

拓扑排序是有向无环图中的一个常见问题,在图中的点存在一定的顺序的场景下,就会用到拓扑排序问题。
拓扑排序定义:在一个有向无环图中,如果有一条边(u,v),则说明u和v之间存在一种依赖关系u–>v(v依赖于u),通俗的解释为,只有u点被访问后,v点才可以被访问。
在宏观上可以转换为:求一个序列,保证对每一条(u,v),都满足u先于v被访问。
为了解决拓扑排序问题,我们引入Kahn算法----一种十分优雅的拓扑排序算法。
Kahn的思路:维护两个集合S和L(S集合是入度为0的访问集合,L是答案序列集合),初始化将入度为0的点加入S中。每次从S中取出一个点放入L,删除从该点出发的每一条边,并更新相关点的入度,如果有点入度减为0,则将其加入S中。直至S中没有点,并判断结果。如果图中仍然存在边,说明该图中有环路,无拓扑序。否则L中的序列即为拓扑序。
Kahn实现:利用队列实现S,容器vector实现L,按照上述算法的步骤进行操作即可。如果希望输出最小字典序的拓扑序,可以使用优先级队列,每次从优先级队列中选取关键字最小的点
Kahn源码实现:

void Kahn()
{
    
    
	while(!q.empty()) q.pop(); 
    for(int i=1;i<=point_cnt;i++)
    {
    
    
        if(in_edge[i]==0)
        {
    
    
            q.push(-i);
        }
    }
    while(!q.empty())
    {
    
    
        int x=-q.top();
        q.pop();
        vec.push_back(x);
        for(int i=head[x];i>=0;i=edges[i].nxt)
        {
    
    
            int y=edges[i].des;
            in_edge[y]--;
            if(in_edge[y]==0)
            {
    
    
                q.push(-y);
            }
        }
    }
}

题目概述

众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。

INPUT&输入样例

输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。
输入样例:

4 3
1 2
2 3
4 3

OUTPUT&输出样例

给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
输出样例:

1 2 4 3

题目重述

给定一个图和多条边,且已知该图为有向无环图。给定的边形式是A B,且有如果A B和B C同时成立,则可以推出有A C。题目是求出一个序列,保证对每一条边A B或传递关系A C,满足在序列中A在C之前和A在B之前,

思路概述

分析题意初见传递,可能会将该题目归类到传递问题从而想到Floyd。但再看题目的求解序列,明确要求是要相对边有序,这和拓扑排序的要求一致,因此可以直接将该题目归类到拓扑排序中。考虑拓扑排序的特点,若有A B,即表示序列中A在B之前,且有B C,表示序列中B在C之前。那么显然可得A一定在C之前,题目的要求都可以满足。只需要将题目套入Kahn算法求解即可。
回看拓扑序和Kahn的过程,我们可以总结出拓扑序问题的判断特征:边有序

问题源码

#include<iostream>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
const int M=1e4;

struct Edge
{
    
    
    int des,nxt;
}edges[M];
int head[M];
int edge_cnt;
int point_cnt;
int in_edge[M];//入度数组
priority_queue<int> q;//最大堆(字典序最小传入负的下标即可)
vector<int> vec;
void init()
{
    
    
    edge_cnt=0;
    for(int i=0;i<=point_cnt;i++)
    {
    
    
    	head[i]=-1;
		in_edge[i]=0;
	}
}
void add(int x,int y)
{
    
    
    edge_cnt++;
    edges[edge_cnt].des=y;
    edges[edge_cnt].nxt=head[x];
    head[x]=edge_cnt;
    in_edge[y]++;
}

void Kahn()
{
    
    
	while(!q.empty()) q.pop(); 
    for(int i=1;i<=point_cnt;i++)
    {
    
    
        if(in_edge[i]==0)
        {
    
    
            q.push(-i);
        }
    }
    while(!q.empty())
    {
    
    
        int x=-q.top();
        q.pop();
        vec.push_back(x);
        for(int i=head[x];i>=0;i=edges[i].nxt)
        {
    
    
            int y=edges[i].des;
            in_edge[y]--;
            if(in_edge[y]==0)
            {
    
    
                q.push(-y);
            }
        }
    }
}

int main()
{
    
    
    int cat_number;
    int match_number;
    while(cin>>cat_number>>match_number)
    {
    
    
    	while(!vec.empty()) vec.clear();
    	point_cnt=cat_number;
    	init();
    	int cin_x,cin_y;
    	for(int i=0;i<match_number;i++)
    	{
    
    
        	scanf("%d %d",&cin_x,&cin_y);
        	add(cin_x,cin_y);
    	}
    /*for(int i=1;i<=point_cnt;i++)
    {
        printf("%d:",i);
        for(int j=head[i];j>=0;j=edges[j].nxt)
        {
            printf("%d ",edges[j].des);
        }
        printf("in:%d\n",in_edge[i]);
    }*/
    	Kahn();
    	for(int i=0;i<vec.size()-1;i++)
    	cout<<vec.at(i)<<" ";
    	cout<<vec.at(vec.size()-1)<<endl;
	}
     return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43942251/article/details/105493783