9.5.1 拓扑排序及LeetCode题目 —— Course Schedule II & Minimum Height Trees

首先解释AOV网的概念:

用一个有向无环图DAG表示一项工程或项目,我们用顶点表示活动,用弧\边表示活动之间的优先关系。这样的图称为顶点表示活动的网即AOV网(Active on Vertex Network)。

再看一下拓扑排序topologicalSort

对于任何有向图而言,其拓扑排序为其所有结点的一个线性排序(对于同一个有向图而言可能存在多个这样的结点排序)。该排序满足这样的条件——对于图中的任意两个结点uv,若存在一条有向边从u指向v,则在拓扑排序中u一定出现在v前面。

拓扑排序主要用来解决有向图中的依赖解析(dependency resolution)问题。

拓扑排序算法:

从AOV网中选择一个入度为0的顶点然后删除此顶点,并删除以此顶点为起点的边。重复此步骤,直到输出全部顶点或AOV网中不存在入度为0的顶点为止。

我们可以用广度优先遍历算法来完成拓扑排序。

  1. 新建一个数组,根据已知的邻接表或边,保存每一个结点的入度。
  2. 选取入度为0的结点加入队列。
  3. 从队列中取出一个点,对于遍历过的每个结点,查找其出度表,出度表中的节点因为该点的删除而少了一条边,更新这些点的入度减1,如果入度变为0,也加入到队列中。
  4. 重复步骤3,直到遍历完所有的结点。
  5. 如果无法遍历完所有的结点,则意味着当前的图不是有向无环图。不存在拓扑排序。

拓扑排序的原理和代码都很简单,下面我们看两道LeetCode的题目学习一个。

210. Course Schedule II

There are a total of n courses you have to take labelled from 0 to n - 1.

Some courses may have prerequisites, for example, if prerequisites[i] = [ai, bi] this means you must take the course bi before the course ai.

Given the total number of courses numCourses and a list of the prerequisite pairs, return the ordering of courses you should take to finish all courses.

If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array.

题解:

就是一道给活动(课程)排序的题目,活动之间有依赖关系。结合代码理解上面的算法思路,首先得出图的出度表(邻接表)和入度值表,然后一个个删除入度为0 的点,order里就是拓扑排序的一种结果。

时间复杂度:初始阶段,要检查所有顶点一次,排序时每个点入栈出栈一次,可以认为时间复杂度是O(n)

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        graph = [[] for i in range(numCourses)]
        num_in = [0 for i in range(numCourses)]
        for edge in prerequisites:
            a, b = edge[0], edge[1]
            graph[b].append(a)
            num_in[a] = num_in[a] + 1
        order = []
        stack = []
        for i in range(numCourses):
            if num_in[i] == 0:
                stack.append(i)
                order.append(i)
        while stack:
            course = stack.pop()
            for out in graph[course]:
                num_in[out] = num_in[out] - 1
                if num_in[out] == 0:
                    stack.append(out)
                    order.append(out)
        if len(order) == numCourses:
            return order
        return []

310. Minimum Height Trees

A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.

Given a tree of n nodes labelled from 0 to n - 1, and an array of n - 1 edges where edges[i] = [ai, bi] indicates that there is an undirected edge between the two nodes ai and bi in the tree, you can choose any node of the tree as the root. When you select a node x as the root, the result tree has height h. Among all possible rooted trees, those with minimum height (i.e. min(h))  are called minimum height trees (MHTs).

Return a list of all MHTs' root labels. You can return the answer in any order.

The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.

题解:

这道题蛮有意思,我这里学习了题解里的思路,用拓扑排序的思想解决。由于是无向图,所以从度=1的点开始入栈,逐步删除,直至剩下1、2个点。

解释一下剩下的1-2个点的情况,我们按度=1的条件删除点,最后只能出现两种情况,可以看题目给的示例,例1剩下一个点且度=0,例2,剩下两个点且度=1。

所以按照这个思路剩下<=2个点时即为结果。这里我们用类似广度优先遍历的方法,保证最后剩在stack里的就是最终结果。

 

class Solution:
    def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
        if n == 1:
            return [0]
        elif n == 2:
            return [0, 1]
        from collections import defaultdict
        graph = defaultdict(list)
        num_in = [0 for i in range(n)]
        for edge in edges:
            graph[edge[0]].append(edge[1])
            graph[edge[1]].append(edge[0])
            num_in[edge[0]] = num_in[edge[0]] + 1
            num_in[edge[1]] = num_in[edge[1]] + 1
        stack = []
        cnt = n
        # 将第一批度=1的入栈
        for i in range(n):
            if num_in[i] == 1:
                stack.append(i)
        while stack:
            tmp = []
            while stack:
                node = stack.pop()
                cnt -= 1
                # 删除这些点及相关边,将下一批度=1的入栈
                for out in graph[node]:
                    num_in[out] = num_in[out] - 1
                    if num_in[out] == 1:
                        tmp.append(out)
            stack = tmp
            if cnt == 2 or cnt == 1:
                return stack

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值