java面试18-创建图的过程

132 阅读3分钟
List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) { 
    // 图中共有 numCourses 个节点 
    List<Integer>[] graph = new LinkedList[numCourses];
    for (int i = 0; i < numCourses; i++) { 
        graph[i] = new LinkedList<>(); 
        } 
    for (int[] edge : prerequisites) { 
    int from = edge[1], to = edge[0]; 
    // 添加一条从 from 指向 to 的有向边
    // 边的方向是「被依赖」关系,即修完课程 from 才能修课程 to     graph[from].add(to);
     } 
    return graph; 
    }

分析:

  • 创建一个邻接表,使用的是list的数组的形式,每一个链表中存储着对应节点的信息
  • 使用for循环,进行初始化数组(一个数组必须进行初始化)graph[i] = new LinkedList<>(); 对应的每一个点都是一个链表进行初始化
  • 填充对应一个节点的链表信息,二维数组中一个元素描述的是两个节点的关系,将其对应的节点的链表进行填充

总结:

邻接表分为两部分:第一是数组,第二是链表

  • 建一个链表数组,初始化大小为课程的数组,即节点数
  • 给每一个节点创建链表,节点是课程的数目,采用for循环

visited 记录哪些节点被遍历过,而 onPath 记录当前递归堆栈中有哪些节点,它们的作用不同,所以并不重复。

类比贪吃蛇游戏,visited 记录蛇经过过的格子,而 onPath 仅仅记录蛇身。onPath 用于判断是否成环,类比当贪吃蛇自己咬到自己(成环)的场景。

boolean[] onPath;
boolean[] visited;
boolean hasCycle = false;
void traverse(List<Integer>[] graph, int s) { 
    if (onPath[s]) { 
        // 发现环!!! 
        hasCycle = true; 
    } 
    if (visited[s] || hasCycle) { 
        return;
    } 
    // 将节点 s 标记为已遍历 
    visited[s] = true; 
    // 开始遍历节点 s 
    onPath[s] = true; 
    for (int t : graph[s]) {
        traverse(graph, t); 
    } 
    // 节点 s 遍历完成 
    onPath[s] = false;
}

使用OnPath数组可减少计算量,只维护当前visited进入时的节点开始,结束时,将Onpath进行变为false。相当于哨兵,当再次出现Onpath时候,相当于贪吃蛇咬住尾巴的时候

进阶:

返回成环时候的节点 类似于判断链表成环,快慢指针,返回相遇的时候的点

拓扑排序

拓扑排序是指对一个有向无环图的节点进行排序之后得到的序列
如果存在一条从节点 A 指向节点 B 的边,那么在拓扑排序的序列中节点 A 出现在节点 B 的前面
一个有向无环图可以有一个或多个拓扑排序序列,但无向图或有环的有向图都不存在拓扑排序。
一种常用的拓扑排序算法是每次从有向无环图中取出一个入度为 0 的节点添加到拓扑排序序列之中,然后删除该节点及所有以它为起点的边。重复这个步骤,直到图为空或图中不存在入度为 0 的节点

  • 如果最终图为空,那么图是有向无环图,此时就找到了该图的一个拓扑排序序列
  • 如果最终图不为空并且已经不存在入度为 0 的节点,那么图中一定有环(可以使用拓扑排序来判断一个有向图是否有环)。
主要应用的问题
  • 大一时一定要修完这门课,大二才学第二门课,这种排课问题就是拓扑排序问题。
总结:
  • 采用DFS,不会涉及到出度和入度的问题(深度优先遍历,采用递归的方式)
  • 采用BFS,主要思路如下 (采用队列的方式)拓扑排序,判断是否有环 1、构建邻接表,边的方向为被依赖关系
    2、构建入度数组,用来记录每个节点的入度,这个入度数组是根据关系的二维数组进行创建的 indegree[to]++指向to的元素入度++
    3、根据入度初始化队列中的节点,遍历所有的节点,将入度为0的节点加入到队列中
    4、如果队列不为空,开始弹出队列,使用一个全局变量记录弹出队列的个数
    5、开始遍历与入度为0相邻的节点,使用for(int next:graph[cur]),遍历的next入度--,当它为0则加入队列 6、判断count的数量是否等于节点的数量,等于则无环,不等则有环 7、入度为0进队列。开始遍历减入度,若再为0进队列,最后count比较节点数

使用map集合来创建表

Map<Integer,List<Integer>> buildGraph(int numCourses,int [][]pre){
    //初始化数组
    //List<Integer>[] graph= new LinkedList<>(); 
    Map<Integer,List<Integer>> graph = new HashMap<>();
    //初始化链表
    //graph[i]=new LinkedList<>();
    for(int i = 0;i < numCourses;i++){
        garph.put(i,new LinkedList<>());
    }
    //将关系初始化
    for(int[] edge:pre){
        int from=edge[1];
        int to =edge[0];
        graph.get(form).add(to);
    }
}