任务管理系统算法-Kahn’s algorithm for Topological Sorting(一)

本文介绍如何利用Kahn’s algorithm进行任务管理系统的拓扑排序。通过设计Graph类存储任务依赖关系,实现任务的入度计算,并使用队列进行任务排序。在循环中不断取出入度为0的任务,直到所有无环任务完成。若有未完成任务,则表示存在依赖环。

上一遍分析了如何设计任务管理系统的算法,拓扑排序之任务管理系统思路设计

今天我们就利用Kahn’s algorithm for Topological Sorting来实现任务管理系统算法。

设计一个数据结构Graph类来储存各个task之间的依赖关系,并根据每个task相互的依赖关系找出任务排列顺序。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Graph {
	private int V;    // No. of vertices
	private int minimumLevel;
	private int tasksInLevel;    // store the number of tasks in the same level
	private List<ArrayList<Integer>> adj;
	private List<String> tasks;
	private boolean isUnique = true;
	private boolean isCircle = false;
	private Queue<Integer> mCircleQueue = new LinkedList<Integer>();   // store the circle tasks in order
	private boolean isFound = false;    // If we find circle, then isFound is true
	
	public Graph() {
		this.V = 0;
		adj = new ArrayList<ArrayList<Integer>>();
		tasks = new ArrayList<String>();
	}
	
	public Graph(String loadTasks[]) {
		this.V = loadTasks.length;
		adj = new ArrayList<ArrayList<Integer>>();
		for(int i = 0; i < V; i++) {
			adj.add(new ArrayList<Integer>());
		}
		tasks = new ArrayList<String>();
		for(String task : loadTasks) {
			tasks.add(task);
		}
	}
	
	public void addTask(String s) {
		if(!tasks.contains(s)) {
			adj.add(new ArrayList<Integer>());
			this.V++;
			tasks.add(s);
		} else {
			System.out.println("Task " + s + " is already in the system, please add another task or quit.");
		}
	}
	
	// Add dependency for last added task
	public void addDependency(String s) {
		if(!tasks.contains(s)) {
			System.out.println("Task " + s + " is not in the system, please try another task as dependency.");
		} else {
			int u = tasks.indexOf(s);
			int v = tasks.size() - 1;
			addEdge(u, v);
		}
	}
	
	/**
	 * Add dependency for specific task
	 * @param dependency
	 * @param task
	 */
	public void addDependency(String dependency, String task) {
		if(!tasks.contains(dependency)) {
			System.out.println("Task " + dependency + " is not in the system, please try another task.");
		} else if(!tasks.contains(task)) {
			System.out.println("Task " + task + " is not in the system, please try another task.");
		} else {
			int u = tasks.indexOf(dependency);
			int v = tasks.indexOf(task);
			addEdge(u, v);
		}
	}
	
//	public void makeTaskDependOnLast(String s) {
//		if(!tasks.contains(s)) {
//			System.out.println("Task " + s + " is not in the system, please try another task.");
//		} else {
//			int u = tasks.size() - 1;
//			int v = tasks.indexOf(s);
//			addEdge(u, v);
//		}
//	}
	
	/**
	 * Add an edge to graph
	 * @param u is dependency
	 * @param v is depend on u
	 */
	public void addEdge(int u, int v) {
		if(!adj.get(u).contains(v))
			adj.get(u).add(v);
	}
	
	public void topologicalSort() {
		isUnique = true;
		isCircle = false;
		minimumLevel = 0;
		tasksInLevel = 0;
		// Create an array to store all vertices' indegrees. 
		int indegree[] = new int[V];
		
		// Find all indegree for all vertices
		for(int i = 0; i < V; i++) {
			ArrayList<Integer> temp = adj.get(i);
			for(int node: temp) {
				indegree[node]++;
			}
		}
		
		// A queue store all vertices which indegree are 0
		Queue<Integer> queue = new LinkedList<Integer>();
		for(int i = 0; i < indegree.length; i++) {
			if(indegree[i] == 0) {
				queue.add(i);
			}
		}
		
		tasksInLevel += queue.size();
		if(tasksInLevel > 0) 
			minimumLevel++;
		
		if(queue.size() > 1) {
			isUnique = false;
		}
		
		int count = 0;
		// ArrayList to store topological sort result
		ArrayList<Integer> topOrder = new ArrayList<Integer>();
		while(!queue.isEmpty()) {
			int u = queue.poll();
			topOrder.add(u);
			
			count++;
			
			for(int node : adj.get(u)) {
				indegree[node]--;
				if(indegree[node] == 0) {
					queue.add(node);
				}
			}
			
			tasksInLevel--;
			if((tasksInLevel) == 0) {
				tasksInLevel += queue.size();
				if(tasksInLevel > 0)
					minimumLevel++;
			}
			
			if(isUnique && queue.size() > 1) {
				isUnique = false;
			}
		}
		
		if(count != V) {
			isCircle = true;
			// An ArrayList store all tasks which are in circle
			Queue<Integer> tasksRemain = new LinkedList<Integer>();
			
			System.out.println("There is NO valid ordering of task.");
			System.out.print("There is a cycle: ");
			for(int i = 0; i < indegree.length; i++) {
				if(indegree[i] > 0) {
					tasksRemain.add(i);
				}
			}
			while(!tasksRemain.isEmpty()) {
				mCircleQueue.clear();
				mCircleQueue.add(tasksRemain.poll());
				searchCircle(mCircleQueue.peek(), indegree);
				if(isFound) {
					isFound = false;
					break;
				}
			}
			while(!mCircleQueue.isEmpty()) {
				System.out.print(tasks.get(mCircleQueue.poll()) + " ");
			}
			System.out.println();
		} else {
			// Print topological order    
			System.out.println("A valid ordering of tasks is as follows:");
	        for(int i : topOrder) { 
	            System.out.print(tasks.get(i)+" ");
	        } 
	        System.out.println();
	        if(!isUnique()) {
	        	System.out.println("This is NOT the only valid ordering of task.");
	        } else {
	        	System.out.println("This is the ONLY valid ordering of task.");
	        }
	        System.out.println("The minimum number of levels is "+ minimumLevel + ".");
		}
	}
	
	private void searchCircle(int u, int[] indegree) {
		if(adj.get(u).size() > 0) {
			for(int dependency : adj.get(u)) {
				if(indegree[dependency] > 0 && !isFound) {
					
					if(mCircleQueue.element().equals(dependency)) {
						isFound = true;
						mCircleQueue.add(dependency);
						
						return;
					} else {
						mCircleQueue.add(dependency);
						searchCircle(dependency, indegree);
						if(!isFound)
							((LinkedList<Integer>) mCircleQueue).removeLast();
					}
				} 
			} 
		} 
	}

	public boolean isUnique() {
		return isUnique;
	}

	public void setUnique(boolean isUnique) {
		this.isUnique = isUnique;
	}

	public boolean isCircle() {
		return isCircle;
	}

	public void setCircle(boolean isCircle) {
		this.isCircle = isCircle;
	}
	
	
}

adj是List<ArrayList<Integer>>类用于存储各个任务之间的依赖关系

/**
	 * Add an edge to graph
	 * @param u is dependency
	 * @param v is depend on u
	 */
	public void addEdge(int u, int v) {
		if(!adj.get(u).contains(v))
			adj.get(u).add(v);
	}

在addEdge方法里体现了u和v之间的依赖关系,即u是v的前提,u必须要在v之前完成,在图里面可以表示为u->v

在topologicalSort()方法里,通过遍历adj数据结构找到各个task的入度indegree.

// Find all indegree for all vertices
		for(int i = 0; i < V; i++) {
			ArrayList<Integer> temp = adj.get(i);
			for(int node: temp) {
				indegree[node]++;
			}
		}

例如u->v是u和v之间的依赖关系,v依赖于u,那么在这里v的入度是1,u是0,v在这里有一个箭头指向自己,那么入度为1.

入度数目取决于当前task被多少箭头指向,也就是当前task依赖于多少个其他task。

// A queue store all vertices which indegree are 0
		Queue<Integer> queue = new LinkedList<Integer>();
		for(int i = 0; i < indegree.length; i++) {
			if(indegree[i] == 0) {
				queue.add(i);
			}
		}
		
		tasksInLevel += queue.size();
		if(tasksInLevel > 0) 
			minimumLevel++;
		
		if(queue.size() > 1) {
			isUnique = false;
		}
		
		int count = 0;
		// ArrayList to store topological sort result
		ArrayList<Integer> topOrder = new ArrayList<Integer>();
		while(!queue.isEmpty()) {
			int u = queue.poll();
			topOrder.add(u);
			
			count++;
			
			for(int node : adj.get(u)) {
				indegree[node]--;
				if(indegree[node] == 0) {
					queue.add(node);
				}
			}
			
			tasksInLevel--;
			if((tasksInLevel) == 0) {
				tasksInLevel += queue.size();
				if(tasksInLevel > 0)
					minimumLevel++;
			}
			
			if(isUnique && queue.size() > 1) {
				isUnique = false;
			}
		}

queue就是储存入度为0的tasks,topOrder储存的是最后topological sort的顺序也就是任务完成顺序。

在while循环里每次poll queue里面的一个task储存到topOrder中,当然这个task入度为0,并且将和这个task有依赖关系的其他task的入度减1,因为我们已经开始把这个task取出并加入到将要完成的任务序列中,相应的indegree表也要更新一下,每次取出的task都可能会产生新的indegree为0的tasks。

while循环就是重复以上步骤,每次从queue取出入度为0的task加入到topOrder中,然后产生的新的入度为0的tasks加入到queue中,在while循环中不断取出入度为0的task直到queue已经没有task了,说明根据依赖关系能完成的tasks全部取出了。

此时如果while循环结束还有task存在我们加入到task manager系统中,也就是代码中的count如果小于V(tasks总数目),说明这个任务依赖关系存在环,也就是有一部分tasks彼此依赖,我们不能找到剩下的任何task不依赖任务task,从而不能完成剩下的tasks。

这个任务管理系统还有寻找任务等级,找出任务依赖中存在的环等功能,下一篇再写。

<think>首先,用户的问题是:“拓扑排序Kahn算法介绍及应用”,意思是介绍拓扑排序的Kahn算法及其应用。用户指定了Kahn算法,所以我需要重点关注这个算法。 从系统级指令中,我有些约束: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 结构清晰,帮助用户逐步解决问题。 回顾用户提供的引用内容: - 引用[1]和[2]介绍了拓扑排序和两种算法Kahn算法和DFS算法- 引用[3]给出了Python代码实现Kahn算法- 引用[4]讨论了如果有环的情况。 - 引用[5]提供了C语言的代码片段。 我的回答需要基于这些引用,但用户说“上述引用仅供参考”,所以我应该使用这些信息来构建可信的回答,但不能直接复制。 关键点: - **Kahn算法定义**:从引用[1]、[2]和[4],Kahn算法通过不断移除入度为0的节点来实现排序。如果有向图存在环,就无法完成排序。 - **步骤**: 1. 初始化个队列(或栈),将所有入度为0的节点加入队列。 2. 当队列不为空时,取出个节点,输出它。 3. 遍历该节点的所有邻居,减少它们的入度(因为移除了依赖)。 4. 如果某个邻居的入度变为0,将其加入队列。 5. 最后,如果输出的节点数不等于总节点数,说明存在环。 - **应用**:拓扑排序用于有向无环图(DAG),例如编译器和构建系统的任务调度、课程安排、项目管理等。引用[2]和[4]提到检测依赖关系。 - **代码示例**:引用[3]提供了Python代码,我可以基于这个给出个清晰的例子。 回答结构: 1. **简述拓扑排序**:解释什么是拓扑排序及其目的。 2. **介绍Kahn算法**:详细描述算法的步骤。 - 使用行内数学表达式来表示边,例如$u \rightarrow v$。 3. **算法实现**:提供个简单的代码示例(如Python),参考引用[3]。 4. **应用场景**:列出Kahn算法的常见应用。 5. **检测环**:强调如何在算法中检测环。 6. **生成相关问题**:在末尾添加相关问题。 确保使用中文回答。 添加引用标识:在描述算法或应用时,自然地引用来源。例如,提到算法定义时引用[1]或[2]。 完整回答大纲: - 引言:拓扑排序概述。 - Kahn算法描述: - 步骤分解。 - 时间复杂度(可能提到,参考引用)。 - 代码示例。 - 应用。 - 环检测。 - 相关问题。 生成相关问题: - 类似示例中的格式:"§§相关问题§§"。 - 相关问题: 1. Kahn算法如何检测环? 2. Kahn算法的时间复杂度是多少? 3. 与DFS算法的比较。 4. 在哪些系统中应用拓扑排序? 最后,确保回答真实可靠。 </think>### 拓扑排序Kahn算法介绍及应用 拓扑排序是针对有向无环图(DAG)的种线性排序算法,确保对于每条有向边 $(u \rightarrow v)$,顶点 $u$ 在排序中出现在顶点 $v$ 之前[^2]。它在处理依赖关系问题时非常有用,如任务调度或编译顺序。Kahn算法是拓扑排序的种经典实现,基于入度操作,具有高效性和直观性。以下将从算法介绍、实现步骤、代码示例和应用场景等方面详细说明。 #### 1. Kahn算法介绍 Kahn算法以入度为核心,通过不断移除入度为0的节点来生成拓扑序列。算法的主要思想是: - **入度定义**:顶点的入度指指向该顶点的边数。例如,顶点 $v$ 的入度为入边 $(u \rightarrow v)$ 的数量[^3]。 - **算法步骤**: 1. **初始化**:计算所有顶点的入度,并将入度为0的顶点加入队列(或栈)。 2. **处理节点**:从队列中取出个节点,输出到排序结果中。 3. **更新入度**:遍历该节点的所有相邻节点(即出边指向的节点),并将每个相邻节点的入度减少1。 4. **添加新节点**:如果某个相邻节点的入度变为0,将其加入队列。 5. **终止条件**:重复步骤2-4,直到队列为空。最终,如果输出的节点数不等于图的总节点数,则说明图中存在环,无法进行拓扑排序[^1][^4]。 Kahn算法的时间复杂度为 $O(V + E)$,其中 $V$ 是顶点数,$E$ 是边数。这是因为每个顶点和边只被处理次。算法的优点包括: - 易于实现,适用于大规模图。 - 能直接检测环:如果在结束时输出节点数少于总节点数,表明图有环(引用[3]中的代码逻辑)[^3]。 #### 2. 算法实现示例 以下是个基于Python的Kahn算法实现,参考引用[3]中的示例代码。代码使用字典表示图,键为节点,值为相邻节点列表。 ```python from collections import deque def kahn_topological_sort(graph): # 计算所有节点的入度 in_degree = {node: 0 for node in graph} for node in graph: for neighbor in graph[node]: in_degree[neighbor] += 1 # 初始化队列,加入入度为0的节点 queue = deque([node for node in in_degree if in_degree[node] == 0]) result = [] # 处理节点 while queue: node = queue.popleft() result.append(node) # 更新相邻节点的入度 for neighbor in graph.get(node, []): in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) # 检测环 if len(result) != len(graph): print("图中存在环,无法进行拓扑排序") return [] return result # 示例图:个有向无环图 graph = { 'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': [] } sorted_order = kahn_topological_sort(graph) if sorted_order: print("拓扑排序结果:", " -> ".join(sorted_order)) # 输出: A -> B -> C -> D(或类似序列) ``` 在这个示例中: - 图包含顶点 A、B、C、D,其中 D 依赖 B 和 C。 - 算法输出个合法序列,如 `A -> B -> C -> D`(顺序可能因队列实现而变)。 - 如果图有环,例如添加条边 `D -> A`,则算法会检测并返回空列表[^3]。 #### 3. Kahn算法的应用 Kahn算法在实际系统中广泛应用,主要针对依赖管理问题: - **编译器构建**:在编译器中,源文件的编译顺序必须满足依赖关系(如模块A依赖模块B)。Kahn算法能生成正确的编译顺序,防止循环引用错误[^2][^4]。 - **任务调度系统**:在自动化构建工具(如Make或Gradle)中,任务需要按依赖执行。例如,测试任务依赖编译任务Kahn算法确保任务线性执行[^1]。 - **课程安排**:教育系统中,课程之间有先修关系(如数学课是物理课的前置)。算法生成可行的课程序列,避免学生遇到未学完的依赖课程[^5]。 - **项目管理**:在项目管理软件中,任务序列需符合依赖链(如设计必须在编码前完成)。Kahn算法帮助优化资源分配和进度规划[^2][^4]。 这些应用依赖于DAG的特性:无环保证排序的唯性或部分顺序性。如果图中存在环,算法能快速检测并报错,避免系统死锁或错误执行[^4]。 总之,Kahn算法是拓扑排序的高效实现,适用于各种依赖排序场景。通过结合入度管理和队列操作,它能在线性时间内完成排序或检测环。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值