C 语言 Kahn 算法2025年1月7日 | 阅读13分钟 Kahn 算法是一种广泛用于有向无环图 (DAG) 拓扑排序的方法。DAG 的拓扑排序是其顶点的线性排序,使得对于从顶点 u 到顶点 v 的每条有向边 uv,u 在排序中都排在 v 之前。这种排序在各种应用中至关重要,例如任务调度、链接器中的符号依赖关系解析以及数据序列化。 Kahn 算法是一种迭代方法,它通过反复移除没有入边(入度为零)的顶点并将其添加到拓扑顺序来工作。该算法以Arthur B. Kahn的名字命名,他于 20 世纪 60 年代引入了该算法。它以其处理拓扑排序的简单性和效率而著称。 Kahn 算法的步骤计算入度:算法开始时,计算图中每个顶点的入度(入边的数量)。此步骤涉及遍历所有边并计算每个顶点的入边数。 初始化队列:初始化一个队列,其中包含所有入度为零的顶点。这些顶点没有依赖关系,可以立即处理。 处理队列:然后算法处理队列中的每个顶点 从队列中移除顶点并将其添加到拓扑顺序中。 对于移除顶点的每个相邻顶点,其入度减一(因为一条边已被“移除”)。 如果相邻顶点的入度变为零,则将其添加到队列中,因为它现在可以被处理。 检查循环:处理完所有顶点后,算法检查添加到拓扑顺序中的顶点数量是否等于图中的总顶点数。如果不相等,则图包含循环,并且无法进行拓扑排序。 方法一:标准队列实现Kahn 算法的拓扑排序可以使用标准队列来实现。此方法涉及跟踪每个顶点的入度,并使用队列来管理没有入边(即入度为零)的顶点。 程序输出 Following is a Topological Sort of the given graph 4 5 0 2 3 1 说明
使用 Kahn 算法进行拓扑排序 计算入度 in_degree 数组存储每个顶点的入度(入边数)。最初,所有入度都设置为零。 算法通过遍历每个顶点的邻接表来遍历图。对于每个相邻顶点,都会增加其入度。此步骤可确保所有顶点的入度都已正确计算。 初始化队列 初始化一个队列来存储入度为零的顶点。这些顶点没有依赖关系,可以立即处理。所有入度为零的顶点都已入队。这是可以安全处理而不会违反拓扑顺序的初始顶点集。 处理队列 算法进入一个循环,在该循环中它从队列的前面反复出队一个顶点。出队的顶点被添加到拓扑顺序中,确保它出现在它指向的任何顶点之前。 对于出队顶点的每个相邻顶点,其入度减一。如果相邻顶点的入度变为零,则将其入队。此步骤确保顶点按正确的顺序处理。 此过程一直持续到队列为空。每个顶点都处理一次,确保所有顶点都包含在拓扑顺序中。
复杂度分析时间复杂度 Kahn 算法的时间复杂度可以分解为几个步骤 图表示初始化 创建图并初始化邻接表数组需要 O(V) 时间,其中 V 是顶点的数量。 计算入度 初始化入度数组需要 O(V) 时间。 计算入度涉及遍历图的所有边。对于每个顶点,我们遍历其邻接表。操作总数与所有邻接表长度之和成正比,等于边数 (E)。因此,此步骤需要 O(E) 时间。 队列初始化 将所有入度为零的顶点入队需要扫描一次入度数组,耗时 O(V)。 处理队列 每个顶点都入队和出队一次。队列的入队和出队操作每个操作耗时 O(1)。由于有 V 个顶点,这些操作加起来耗时O(V) 时间。 递减相邻顶点的入度涉及再次遍历所有边。每条边在其起始顶点出队时处理一次,从而减少其结束顶点的入度。这需要O(E) 时间。 循环检测和最终检查 检查处理过的顶点数是否等于总顶点数只需一次简单的比较,耗时 O(1)。 将这些步骤结合起来,总时间复杂度为:O(V)+O(E)+O(V)+O(E)+O(1)=O(V+E) 因此,Kahn 算法的时间复杂度为 O(V+E),与图中的顶点和边数呈线性关系。这种效率使其适用于大型图。 空间复杂度 空间复杂度分析考虑了算法中数据结构使用的内存 图的表示 图使用邻接表表示。邻接表数组包含 V 个列表,所有列表中的节点总数为 E。因此,邻接表所需的空间为O(V + E)。 入度数组 使用大小为 V 的数组存储每个顶点的入度,这需要O(V) 空间。 Queue 队列存储入度为零的顶点。在最坏的情况下,所有顶点都可能在某个时候入队,这需要O(V) 空间。 拓扑顺序数组 用于存储顶点拓扑顺序的数组也需要 O(V) 空间。 将这些结合起来,算法的总空间复杂度为:O(V+E)+O(V)+O(V)+O(V)=O(V+E) 因此,Kahn 算法的空间复杂度为 O(V+E)。这种线性空间复杂度确保了该算法即使对于大型图也能高效利用内存。 方法二:使用向量的 Kahn 算法使用向量实现 Kahn 算法是实现拓扑排序的另一种方法。此方法是标准队列实现的一种变体,它使用向量来维护零入度顶点的列表。 程序输出 Following is a Topological Sort of the given graph 5 4 1 2 3 0 说明
复杂度分析时间复杂度 Kahn 算法的时间复杂度可以分解为几个不同的阶段 图表示初始化 创建图并初始化邻接表数组需要 O(V) 时间,其中 V 是顶点的数量。 入度计算 初始化入度数组需要O(V) 时间。 计算入度涉及遍历图的所有边。对于每个顶点,我们遍历其邻接表。操作总数与所有邻接表长度之和成正比,等于边数 (E)。因此,此步骤需要 O(E) 时间。 初始化零入度顶点向量 将所有入度为零的顶点入队需要扫描一次入度数组,耗时O(V) 时间。 处理顶点 每个顶点都处理一次。处理过程包括将顶点添加到拓扑顺序,然后遍历其邻接表以更新其邻居的入度。 对于每个顶点 u,遍历其邻接表并递减其邻居的入度需要 O(degree(u)) 时间,其中 degree(u) 是从顶点 u开始的边数。 因此,处理所有顶点和边的总时间为 O(V)(用于顶点)和 O(E)(用于边),使得此步骤为O(V + E)。 循环检测和最终检查 检查拓扑顺序中的顶点数是否等于总顶点数只需一次简单的比较,耗时 O(1)。 将所有这些步骤结合起来,总时间复杂度为:O(V)+O(E)+O(V)+O(V+E)+O(1)=O(V+E) 因此,使用向量的 Kahn 算法的时间复杂度为 O(V + E)。 空间复杂度 空间复杂度分析考虑了算法中数据结构使用的内存 图的表示 图使用邻接表表示。邻接表数组包含 V 个列表,所有列表中的节点总数为 E。因此,邻接表所需的空间为O(V + E)。 入度数组 使用大小为 V 的数组存储每个顶点的入度,这需要 O(V) 空间。 零入度顶点向量 zero_in_degree 向量存储入度为零的顶点。在最坏的情况下,所有顶点都可能添加到该向量中,这需要O(V) 空间。 拓扑顺序数组 用于存储顶点拓扑顺序的数组也需要 O(V) 空间。 将这些结合起来,算法的总空间复杂度为:O(V+E)+O(V)+O(V)+O(V)=O(V+E) 因此,使用向量的 Kahn 算法的空间复杂度为 O(V + E)。这种线性空间复杂度确保了该算法即使对于大型图也能高效利用内存。 |
我们请求您订阅我们的新闻通讯以获取最新更新。