拓扑排序(持续更新)

本文通过两道例题探讨了拓扑排序在解决图论问题中的应用。第一题中,安全节点是指从该节点出发无论走哪条路径都能到达出度为零的节点,通过反向构建图并进行拓扑排序,找到不处于环中的节点即为安全节点。第二题涉及杂物完成时间的最优化,通过拓扑排序确定最优路径并计算最长完成时间。两题都体现了拓扑排序在解决依赖关系问题上的有效性。
摘要由CSDN通过智能技术生成

拓扑排序算法: 拓扑排序就是找到一个入度为0的带你加入队列后,在图中去掉该点及其与它相连的边,然后再词寻找入度为零的点直到所有的点都被遍历,由上面的过程中我们不难想到,如果图中存在环这种结构那么我们是无法遍历所有的节点的,所以拓扑排序的一个重大的应用就是可以找出不存在与环中的点

例题1 在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。

对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。

返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。

该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。(力扣:802)

在这里插入图片描述
首先这道题要理解什么是安全点:首先题目中说明了安全点就是无论然那条路径最终都可以到达出度为零的点,所以处于环中的点肯定就不是安全点,所以我们就是要找到入度为零的点所以自然而然的就想到了拓扑排序,不过这道题需要变通的点就在于将图反序,以原图中出度为零的点作为起点

// 一种新的解题思路:拓扑序列,简单来说其实拓扑序列的作用就是能提取出不处与环中的节点
// 而题目要求我们求什么?就是求出度为零的点,以及最终指向出度为零,且不处于环中的节点
// 所以我们可以将逆序,然后求出以终点为起点的拓扑序列,最终不处于该序列中的点即是处于环中的点
class Solution {
    // 创建一个集合来保存所有安全的起点
    List<Integer> alllist=new ArrayList<Integer>();
    // 创建一个集合来保存返图的节点关系
    List<List<Integer>> regraph=new ArrayList<List<Integer>>();
    public List<Integer> eventualSafeNodes(int[][] graph) {
    //   首先为反图集合赋予空间
    for(int i=0;i<graph.length;i++){
        regraph.add(new ArrayList<Integer>());
    }

    // 将图中的关系保存到反图中去
    for(int i=0;i<graph.length;i++){
        for(int j=0;j<graph[i].length;j++){
             regraph.get(graph[i][j]).add(i);
        }
    }

    // 保存好反图中各节点的入度的关系,也就是原图中该节点的出度的关系
    int[] edge=new int[graph.length];
    for(int i=0;i<graph.length;i++){
        edge[i]=graph[i].length;
    }

    // 进行拓扑排序
    // 创建一个队列进行拓扑排序
    Queue<Integer> queue=new LinkedList<Integer>();
    // 首先找到入度为零的点并将这些节点如队列
    for(int i=0;i<edge.length;i++){
        if(edge[i]==0){
            queue.offer(i);
        }
    }

    // 进行广度优先遍历
    while(!queue.isEmpty()){
        int temp=queue.poll();
        // 将与当前节点相邻的节点的入入度都减一,并且将减一后入度为0的节点加入队列
        List<Integer> temp1=regraph.get(temp);
        for(int k:temp1){
            edge[k]--;
            if(edge[k]==0){
                queue.offer(k);
            }
        }
    }

    // 最后遍历一遍edge数组如果该节点的入度为零则说明该点不处于环中,该点为安全点
    for(int i=0;i<edge.length;i++){
        if(edge[i]==0){
            alllist.add(i);
        }
    }
    return alllist;
    }
}

例题2:杂物
在这里插入图片描述
在这里插入图片描述

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class Main{
   public static void main(String[] args) {
	   Scanner scan=new Scanner(System.in);
	   int n=scan.nextInt();
//	   创建一个集合保存只完成该项项目所需要的时间
	   int[] time=new int[n+1];
	   List<Integer>[] map=new List[n+1];
//	   为图赋予空间
	   for(int i=0;i<=n;i++) {
		   map[i]=new ArrayList<Integer>();
	   }
//	    创建一个数组保存每个点的入度的关系
	   int[] edge=new int[n+1];
//	   利用循环输时间以及图论之间的关系
	   for(int i=1;i<n+1;i++) {
		   int temp=scan.nextInt();
		   time[temp]=scan.nextInt();
		   while(temp!=0) {
                int a=scan.nextInt();
			    if(a==0) break;
//			    因为后面保存的是前置的节点
			    map[a].add(temp);
			    edge[i]++;
		   }
	   }
//	   for(int i=1;i<=n;i++) {
//		   for(int a:map[i]) {
//			   System.out.print(a+" ");
//		   }
//		   System.out.println();
//	   }
//	   创建一个数组保存完成每项杂物所需的时间
	   int[] res=new int[n+1];
//     建图完毕,创建一个队列进行广度优先遍历
	   Deque<Integer> queue=new LinkedList<Integer>();
//	   首先先将入度为0的点入队列
	   for(int i=1;i<n+1;i++) {
		   if(edge[i]==0) {
			   res[i]=time[i];
			   queue.offer(i);
		   }   
	   }
//	   进行拓扑排序
	   while(!queue.isEmpty()) {
		    int temp=queue.poll();
		    for(int a:map[temp]) {
//		    	将入度减一
		    	edge[a]--;
//		    	如果为edge为0入队列
//		    	本项杂物所需的时间为前置杂物所需时间加上本身的时间,注意因该为最长的杂物时间加上自己
		    	res[a]=Math.max(res[a],res[temp]+time[a]);
		    	if(edge[a]==0) {
		    		queue.offer(a);
		    	}
		    }
	   }
        int max=0;
//	   遍历一边找出最长的花费时间
	   for(int i=1;i<=n;i++) {
		   if(res[i]>max) {
			   max=res[i];
		   }
	   }
//	   输出最长的时间
	   System.out.print(max);
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值