UVA 10305 Ordering Tasks(拓扑排序)

拓扑排序实现解析
本文介绍了一种基于拓扑排序的算法实现,用于解决任务排序问题。通过使用二维布尔数组记录任务间的前后依赖关系,并利用入度数组追踪每个任务前的任务数量,最终实现了有效的排序输出。文章还提供了完整的C语言代码示例。

题目大意

有编号 1~n 为任务,给你 m 组数据,表示任务 i 需要在任务 j 之前完成,最后让你输出满足以上情况的结果(可能有多种结果,只要满足即可)。

解题思路

1、我们用 x (xian) 和 h (hou) 来代替题目中的 i 和 j ,以便理解且不容易出错。

2、开一个二维 bool 数组map [] []

输入数据时,每输入一个数据,若 map [x] [h] 为 false,则令 map [x] [h] 为 true,标记这组数据已被输入,防止重复计数。

3、开一个数组 indegree [],记录任务 h 前面有多少任务数。

每输入一个数据,就令 indegree[h] ++

4、开一个数组 ans [] 作为排序结果。

在函数 topo 中,对 indegree [] 进行遍历

如果找到 indegree [t] 为 0,代表这个任务前面没有任务,则将这个任务加到结果数组 ans[] 中,然后令 indegree [t] = -1,标记它已处理。

然后遍历 map [t] [j],若 map [t] [j] 不为 0,证明这个已处理的任务后面跟有任务,则令后面这个跟着的任务的 indegree [j] –

5、最后输出数组 ans [] 即可

复杂度分析

O(n)

算法资料

拓扑排序

心得体会

学会了基础的拓扑排序

代码

#include <stdio.h>
#include <string.h>
const int maxn = 105;

int n, m, x, h;        //定义全局变量 
bool map[maxn][maxn];  //判断这组数据是否被输入过,防止重复计数 
int ans[maxn];         //拓扑排序结果 
int indegree[maxn];    //排在 h前面的有多少 

void topo() {
	//进行拓扑排序 
	int k = 0;
	int t;
	for(int i=1; i<=n; i++) {       //多次遍历
		for(int j=1; j<=n; j++) {   //遍历
			if(indegree[j]==0) {    //若 j前面为空 
				t = j;
				break;
			}
		}
		ans[k++] = t;      //依次加入排序结果中 
		indegree[t] = -1;  //将其标记为已进行拓扑排序 
		for(int j=1; j<=n; j++)  //遍历一次 
			if(map[t][j] != 0)   //如果找到 t在 j前面的 
				indegree[j]--;   //就令 j前面的数量减一,因为 t已经排序完成 
	}
	
	//输出 
	for(int i=0; i<n-1; i++)   //注意最后一个后面不加空格 
		printf("%d ", ans[i]);
	printf("%d\n", ans[n-1]);
}

int main(void) {
	while(scanf("%d %d", &n, &m) != EOF) {
		if(n==0 && m==0)   //结束条件 
			break;
		//初始化 
		memset(map, 0, sizeof(map)); 
		memset(indegree, 0, sizeof(indegree));
		
		for(int i=0; i<m; i++) {
			scanf("%d %d", &x, &h);  //读入先后 
			if(map[x][h]==0) {   //若这组数据没有被输入过 
				indegree[h]++;   //让 h前面的数量加 1 
				map[x][h] = 1;   //标记这组数据已被输入 
			}
		}
		topo();  //进行拓扑排序并输出结果 
	}
	return 0;
}
### 拓扑排序算法的实现与原理 #### 原理概述 拓扑排序是一种针对有向无环图(DAG, Directed Acyclic Graph)的操作方法,其目的是将图中的所有顶点排列成线性顺序,使得对于每一条有向边 \(u \rightarrow v\),\(u\) 都会出现在 \(v\) 的前面[^1]。如果图中存在环,则无法完成拓扑排序。 该算法的核心在于利用节点之间的依赖关系来决定它们在线性序列中的位置。通常情况下,拓扑排序可以用于解决任务调度、项目管理等问题,在这些场景下,某些任务可能需要先于其他任务执行。 #### 实现方式 常见的两种拓扑排序实现分别是基于 **Kahn 算法** 和 **深度优先搜索 (DFS)**: --- ##### Kahn 算法 Kahn 算法通过维护入度表和队列结构逐步构建拓扑序列表。具体过程如下: - 初始化一个数组记录每个节点的入度; - 将所有入度为零的节点加入到队列中; - 不断取出队首节点并将其从图中移除,同时更新与其相邻节点的入度值; - 如果某个节点的入度变为零,则将其加入队列继续处理。 以下是 Python 中基于 Kahn 算法的实现代码示例: ```python from collections import deque def topological_sort_kahn(graph): indegree = {node: 0 for node in graph} # 记录各节点的入度 # 统计每个节点的入度 for u in graph: for v in graph[u]: indegree[v] += 1 queue = deque([node for node in indegree if indegree[node] == 0]) # 入度为0的节点进入队列 result = [] while queue: current_node = queue.popleft() result.append(current_node) for neighbor in graph[current_node]: # 更新邻居节点的入度 indegree[neighbor] -= 1 if indegree[neighbor] == 0: queue.append(neighbor) if len(result) != len(indegree): # 存在环的情况 raise ValueError("Graph has at least one cycle; no valid topological ordering exists.") return result ``` 此版本的时间复杂度为 O(V + E),其中 V 是节点数,E 是边的数量[^2]。 --- ##### DFS 方法 另一种常用的拓扑排序方法是借助深度优先遍历的思想。它通过对每一个未访问过的节点调用递归函数,并在其所有的子节点都被处理完毕后再将当前节点压栈的方式得到最终的结果。 下面是采用 DFS 进行拓扑排序的一个例子: ```python def dfs_topological_sort(graph): visited = set() # 跟踪已访问的节点 stack = [] # 结果存储容器 def visit(node): if node not in visited: visited.add(node) for neighbor in graph.get(node, []): visit(neighbor) stack.insert(0, node) # 当前节点的所有邻接点都已完成时才插入结果集 nodes = list(graph.keys()) for n in nodes: if n not in visited: visit(n) return stack ``` 这种方法同样具有时间复杂度 O(V+E)[^3]。 --- #### 应用实例 假设我们有一个简单的课程安排问题,其中有几门课之间存在着前置条件约束关系。例如,“微积分”必须修完才能学习“物理”,而“代数”又是“微积分”的前提之一。那么可以用下面这样的数据表示这种依赖关系: ```python course_graph = { 'Algebra': ['Calculus'], 'Calculus': ['Physics'], 'Biology': [], 'Chemistry': ['Biology'] } print(topological_sort_kahn(course_graph)) # 输出可能是:['Algebra', 'Calculus', 'Physics', 'Biology', 'Chemistry'] 或者类似的合法顺序 ``` --- #### 注意事项 当输入图为非 DAG 类型或者含有循环路径时,以上任何一种方法都无法正常工作,因为此时不存在有效的拓扑排序方案[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值