拓扑排序,解决谷歌面试题

2025-06-08

拓扑排序,解决谷歌面试题

如果我们想成为一名全栈开发人员,我们可以遵循以下路径:

替代文本

首先,我们学习基础的 HTML 和 CSS,然后学习 JavaScript。之后,这条路径会分为前端开发和后端开发。要成为后端开发人员,我们需要学习数据库,最后成为一名全栈开发人员。

所以这条路是固定的,要学习前端框架,你需要了解 HTML、CSS 和 JavaScript 的基础知识。

组件之间是单向关系,不能先学React再学HTML。

拓扑排序是指事物发生/出现的顺序,在实际生活中有很多用途,例如在处理大数据时,数据以块的形式提供,以便资源利用率达到最优,然后传输处理后的数据。网络中的最大友谊距离(在社交媒体网络上也称为六度分离)是使用拓扑排序来解决的。

现在我们知道了什么是拓扑排序及其用途,让我们来解决 Google 经常问到的一个问题。

X---------------------------------------------X

问题:课程安排

你需要选修的课程一共有 n 门,分别标记为 0 到 n-1。

有些课程可能有先修课程,例如,要修读课程 0,你必须先修读课程 1,这表示为一对:[0,1]

给定课程总数和先修课程对列表,返回完成所有课程应遵循的课程顺序。

给定输入:numCourses(表示需要选修的课程数量)以及 prerequisites 数组(用于确定课程之间的依赖关系)。如果不存在依赖关系,则返回 0。

例如:如果给定数据为 [[2,0],[2,1],[3,2],[4,2],[4,5],[6,3],[6,4]]

我们的图表将如下所示:
替代文本

由此我们可以说可能的顺序可能是:[0,1,5,2,3,4,6]

步骤 1:构建图表

图可以用邻接表/邻接矩阵来表示。两者都很容易实现。在本例中,我们将使用邻接表,因为该图是稀疏图(即许多节点彼此不相连)。

除了我们的图表之外,我们还将维护一个数组,indegree,它将维护节点“i”的先决条件的数量

     var topologyOrder = function(numCourses,prerequisites){

        // create each individual node
        let graph = new Array(numCourses+1);

        // to track the number of dependencies required by each node
        let indegree = new Array(numCourses+1).fill(0);

        for(let i=0;i<numCourses+1;i++){
            graph[i] = []; 
        }

        for(let prerequisite of prerequisites){
            let from = prerequisite[1];
            let to = prerequisite[0];
            graph[from].push(to);
            indegree[to]++;
        }
        console.log(graph);
        console.log(indegree);
  }
Enter fullscreen mode Exit fullscreen mode

操作结束后我们的图表将如下所示:

   graph = [[2]                // 0 -> 2
            [2]                // 1 -> 2
            [3,4]              // 2 -> 3, 2 -> 4
            [6]                // 3 -> 6
            [6]                // 4 -> 6
            [4]                // 5 -> 4
            []]                // 6 -> end   

 indegree = [ 0,               // 0 ie no prerequisites
              0,             
              2,               // 2 courses are required before this
              1,               // 1 course is required
              2,               
              0,
              2 ]
Enter fullscreen mode Exit fullscreen mode

现在我们有了一张图表,它显示了在学习某门课程之前需要完成多少门课程。让我们继续下一步。

广度优先遍历

第一步是选修入度为“0”的课程,即没有先决条件的课程。

此外,我们维护一个队列来处理图中的每个节点,就像我们在 BFS 遍历中所做的那样。

   let queue = [];

   for(let i=0;i<indegree.length;i++){
       if(indegree[i] == 0){
            queue.push(i);
       }
   }
Enter fullscreen mode Exit fullscreen mode

我们的下一步将是处理队列中的每个节点,但在此之前,我们需要确保队列中有要处理的节点。

例如:如果给定的输入是 [[0,1],[1,0]],即 0 -> 1 和 1 -> 0。我们陷入了死锁。

   if(queue.length == 0) return 0;
Enter fullscreen mode Exit fullscreen mode

下一个问题是如何处理节点?同时,我们必须确保流程单向,并且不会重复访问某个节点,否则就会陷入无限循环。

因此我们创建一个数组、一个集合和一个计数器:

   let res = [];                // to store the result
   let visited = new Set();     // to store visited nodes
   let count = 0;               // safety check to ensure all nodes are visited
Enter fullscreen mode Exit fullscreen mode

接下来的步骤是:
步骤 1> 遍历队列,
步骤 2> 弹出一个节点,
步骤 3> 通过将该节点设置为已访问并将其添加到结果中来处理该节点,
步骤 4> 访问当前节点的所有子节点并将其入度减少 1,
步骤 5> 增加计数,
步骤 6> 重复步骤 1 - 5,直到队列为空。

while(queue.length>0){
      // pop a node from queue
      let node = queue.shift();

      // check if it's visited, if it's the return 0
      if(visited.has(node)) return 0;

      // add node to visited set
      visited.push(node);

      // add node to result
      res.push(node);

      //loop over all the nodes require current node as a prerequisite
      for(let n of graph[node]){

          // since current node is processed, decrease the indegree of node "n" by 1
          indegree[n]--;

          // if node "n" indegree equals 0, add it to the queue so that it can be processed
         if(indegree[n] == 0) queue.push(n);
      }

      // incremenet count by 1
      count++;
}
Enter fullscreen mode Exit fullscreen mode

让我们以动画形式查看上述步骤。如果可以的话,请在另一个选项卡中打开 gif 动图,并将每个步骤与上面的代码进行比较。

替代文本

把它们放在一起:

 var topologyOrder = function(numCourses,prerequisites){

       let graph = new Array(numCourses);

       let indegree = new Array(numCourses);

       for(let i=0;i<numCourses;i++){
           graph[i] = []; 
       }

       for(let prerequisite of prerequisites){
           let from = prerequisite[1];
           let to = prerequisite[0];
           graph[from].push(to);
           indegree[to]++;
       }

       let queue = [];

       for(let i=0;i<indegree.length;i++){
            if(indegree[i] == 0){
               queue.push(i);
            }
       }

       if(queue.length == 0) return 0;

       let res = [];                
       let visited = new Set();     
       let count = 0;

       while(queue.length>0){
             let node = queue.shift();
             if(visited.has(node)) return 0;
             visited.add(node);
             res.push(node);
             for(let n of graph[node]){
                  indegree[n]--;
                  if(indegree[n] == 0) queue.push(n);
             }
             count++;
      }

      return count == numCourses ? res : 0;
}
Enter fullscreen mode Exit fullscreen mode

如果您读到这里,非常感谢 :) 我希望您喜欢我的文章。

如果我在某个地方搞砸了或者没有解释清楚,请发表评论。

github:https://github.com/AKHILP96/Data-Structures-and-Algorithms/blob/master/Algorithm/TopologicalSort.js

鏂囩珷鏉ユ簮锛�https://dev.to/akhilpokle/topological-sort-solving-google-interview-question-cb
PREV
使用 Node.Js 读取电子邮件数据
NEXT
设计 Trie 树,解决 Uber 面试题