7-2 任务调度的合理性 (25分)
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。你现在的工作是写程序判定任何一个给定的任务调度是否可行。
输入格式:
输入说明:输入第一行给出子任务数N(≤100),子任务按1~N编号。随后N行,每行给出一个子任务的依赖集合:首先给出依赖集合中的子任务数K,随后给出K个子任务编号,整数之间都用空格分隔。
输出格式:
如果方案可行,则输出1,否则输出0。
输入样例1:
12
0
0
2 1 2
0
1 4
1 5
2 3 6
1 3
2 7 8
1 7
1 10
1 7
输出样例1:
1
输入样例2:
51 42 1 42 2 51 30
输出样例2:
0
此题虽然复杂,但好在只叫我们判断是否构成回路。
解决此题我们需要一个问题——如何才能判断是否构成回路?
这有一个思路:首先把入度为零的结点逐个输出,在输出的同时删除以它为起始点的边,如此循环操作,到最后如果还有点剩下,那么剩下的点就构成回路
根据这个思路,我们可以把程序分为以下几步:
1.根据输入建图(这里一般采用邻接表更好,但为了方便我采用邻接矩阵来表示)
2.遍历图,进行拓扑排序,同时让以它为起点的结点入度减一(相当于删除以它为起始点的边)
注:我们设置inner数组存储入度
3.循环遍历结束,判断inner数组中是否是否所有数都为0,若没有则输出0(表示方案不可行,若全为0则方案可行)
import java.util.Scanner; public class Main { //表示无穷大 final static int MaxInt=1000; static int num; static int[][] Graph; public static void main(String[] args) { initialize(); topSort(); } // 1.根据输入建图(这里一般采用邻接表更好,但为了方便我采用邻接矩阵来表示) private static void initialize(){ int temp,node; Scanner scanner=new Scanner(System.in); //存储结点个数 num=scanner.nextInt(); Graph=new int[num][num]; for (int i=0;i<num;i++){ for (int j=0;j<num;j++){ Graph[i][j]=MaxInt; } } //这里我看错题目了,我以为每行第一个也是数据,结果是表示个数。。。。。唉,所以下次做题前还是先把题目细节弄清楚再来做题 // //注意:读取完会有“”,这会导致无法转换,所以先把这个""消耗掉 // scanner.nextLine(); // for (int i=0;i<num;i++){ // //记录每行的字符串 // String line=scanner.nextLine(); // String[] nodes=line.split(" "); // for (String s:nodes){ // // //同时0表示无节点,所以也要排除 // if (!s.equals("0")){ // //记录转化后的数 // temp=Integer.parseInt(s); // //将有边的结点赋值为1,并且它指的结点值减一才为下标值 // Graph[temp-1][i]=1; // } // // } // } for (int i=0;i<num;i++){ temp=scanner.nextInt(); for (int j=0;j<temp;j++){ node=scanner.nextInt(); Graph[node-1][i]=1; } } } private static void topSort(){ // 2.遍历图,进行拓扑排序,同时让以它为起点的结点入度减一(相当于删除以它为起始点的边) // 注:我们设置inner数组存储入度 // 3.循环遍历结束,判断inner数组中是否是否所有数都为0,若没有则输出0(表示方案不可行,若全为0则方案可行) //不需要初始化,因为创建数组时默认值就为0 int[] inner=new int[num]; //记录结点是否被收录,0表示未被收录,1表示已被收录 int[] collected=new int[num]; //为inner数组赋值 for (int i = 0; i<num; i++){ for (int j=0;j<num;j++){ if (Graph[i][j]==1){ inner[j]++; } } } //定义开关 boolean flag=true; while (flag){ //记录此次遍历时收录结点的格式 int count=0; for (int i=0;i<num;i++){ if (inner[i]==0&&collected[i]==0){ collected[i]=1; count++; for (int j=0;j<num;j++){ if (Graph[i][j]==1){ inner[j]--; } } } } //若count为0表示此次循环未收录结点,可退出循环 if (count==0){ flag=false; } } for (int i=0;i<num;i++){ if (inner[i]!=0){ System.out.println(0); return; } } System.out.println(1); } }