Leetcode刷题记录 2023/10/16

207/210:课程表

有多门课程需要选修,给定课程总数和选修课程的依赖关系集合([a,b],即若需要选修课程a则需先选修课程b),求能否找到一个学完所有课程的学习顺序。

以课程为点,依赖关系为单向边建图,题目转化为求图中是否存在环。
验证图中是否有环的最简单方式是拓扑排序。

给定一个包含 n n n 个节点的有向图 G G G,我们给出它的节点编号的一种排列,如果满足:
对于图 G G G 中的任意一条有向边 ( u , v ) (u,v) (u,v) u u u 在排列中都出现在 v v v 的前面。
那么称该排列是图 G G G 的「拓扑排序」。
根据上述的定义,我们可以下结论:
如果图 G G G 中存在环,那么图 G G G 不存在拓扑排序( u u u v v v前也在 v v v后)

因此,题目转化为建图后求图是否存在拓扑排序。
建图我一般使用邻接表的方式建图,但这次也学到了如何使用C++动态容器建图。(之后算法阐述中使用C++动态容器法)

//传统邻接矩阵
const int maxn = 100005;
struct edge{
    int next, to;
}w[maxn];
int head[maxn], cnt = 0;
void Addedge(int x,int y){
    w[++cnt].to = y; w[cnt].next = head[x]; head[x] = cnt;
} 
//C++动态容器法
vector<vector<int>> edges;
void Addedge(int x,int y){edges[x].emplace_back(y);}

求拓扑排序存在两种思路,DFS与BFS。
DFS法中,一个点有3种状态——【未搜索】【搜索中】和【已搜索】。
由于拓扑排序的特性,一个深度优先搜索的过程就是我们寻找可行拓扑排列的过程;因此我们算法的流程如下:

  • 从任意一个【未搜索】的点开始出发,将其状态置为【搜索中】;
  • 在向下搜索的过程中,每次遇到【未搜索】的子节点就对其进行迭代DFS,待其搜索完毕生成子序列后回溯。
  • 如果在向下搜索的过程中发现【搜索中】的节点,说明图中有环,此时直接不存在拓扑排序。
  • 如果搜索的过程中发现了【已搜索】的节点,则忽略,对结果无影响;
  • 搜索完毕一个点后将其置为【已搜索】。

具体看代码。

//DFS Topology
//vis:0未搜索,1搜索中,2已搜索
void DFS(int x){
	vis[x] = 1;
    for(int y: edges[x]){
    	if(vis[y] == 0){
            DFS(y);
            if(!valid) return;
        }
        if(vis[y] == 1){
            valid = false;
            return;
        }
    }
    vis[x] = 2;
}
for(int i = 0; i < numCourses && valid; i++){
	if(!vis[i]) DFS(i);
}

与之相对的是BFS算法。BFS算法不模拟拓扑寻找顺序,而是统计每个点的入度。若一个图存在拓扑排序,则一定有一个开始点,这个点没有任何依赖——即入度为零。BFS每次找出这个点之后,将其从图中删去,移除它的所有出边,使得其后继节点“少了一门先修课程”。如果存在拓扑排序,则在移除点之后紧邻的点应当变为入度为0的“可修课”。不断重复这个流程,直到:

  • 所有点都已经变为出度为0的点,说明图中无环,存在拓扑序,且拓扑序正是每一次移除点的顺序
  • 某些点出度不为0,说明图中有环,环彼此依赖导致出度无法归零。
//在Addedge的过程中要统计入度。
queue<int> q;
vector<int> ans;//记录拓扑序
for(int i=0;i<numCourses;i++) if(!indegree[i]) q.push(i);//找初始点
while(!q.empty()){
	int x = q.front(); q.pop();
	ans.push_back(x);
	for(int y: edges[x]){
    	indegree[y]--;
        if(indegree[y] == 0) q.push(y);
    }
}
if(ans.size()!=numCourses) return {};//存在环
else return ans;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值