力扣-210-课程表Ⅱ

拓扑排序的定向模板题,AOV网

直达链接

210 和 207 如出一辙,但是 210 需要构造并返回拓扑序列,更适合学习

拓扑排序

拓扑序列的定义如下:

有向图中所有顶点形成的线性序列,对于两个任意顶点 Vi 和 Vj,如果有向图中存在一条 Vi 到 Vj 的路径,那么在线性序列中 Vi 一定在 Vj 前面

现在就是给出了 Vi 和 Vj 的顺序关系,需要重构有向图并返回一个拓扑排序

构造步骤

  1. 从图中选择一个入度为零的点
  2. 输出该顶点,从图中删除此顶点及其所有的出边

重复上面两步,直到所有顶点都输出,拓扑排序完成,或者图中不存在入度为零的点,此时说明图是有环图,拓扑排序无法完成,陷入死锁

深度优先

class Solution {
private:
	// 邻接表
	vector<vector<int>> edges;
	vector<int> visited;
	// 用数组模拟栈
	// 应该是处于方便考虑,因为结果要返回一个数组,如果用栈,再把栈中元素腾到数组中麻烦
	vector<int> result;
	bool notExistCircle = true;
public:

	void dfs(int u) {
		visited[u] = 1;

		for (int node : edges[u]) {
			if (visited[node] == 0) {
				dfs(node);
				if (!notExistCircle) return;
			}
			else if (visited[node] == 1) {
				notExistCircle = false;
				return;
			}
		}
		visited[u] = 2;
		result.push_back(u);
	}

	vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
		edges.resize(numCourses);
		visited.resize(numCourses);
		// (i,j)学i之前要先学j
		// 二维数组第i个数组元素表示数字i指向了哪些数字,即j在哪些数字前面
		for (const auto& info : prerequisites) edges[info[1]].push_back(info[0]);

		// 每次挑一个“未搜索”的节点,开始深度优先搜索
		for (int i = 0; i < numCourses && notExistCircle; i++) if (!visited[i]) dfs(i);

		// 循环结束,检查是不是有环
		if (!notExistCircle) return {};

		// 将模拟栈的数组倒置
		reverse(result.begin(), result.end());
		return result;
	}
};

Shpoee 面试

太尬了,面试遇到结果完全没印象,做不出来,大眼瞪小眼硬着头皮写了 40 分钟

而且有一点不同的是,面试并没有注意到题目要求是任意输出一个还是输出所有可能的结果

面试官给到的思路也和官方题解不完全一致:

  1. 首先扫描图中入度为 0 的节点,并将其放到结果数组中。如果同时存在多个,比如两个,那么结果数组也分裂成两个,也就是说不只是输出一个,而是将所有可能的拓扑排序都输出。
  2. 将弹出的入度为 0 的节点指向的节点的入度减一,重复上述过程直到输出所有的节点(在没有环的前提下)

邻接表

类似于二维数组的结构,记录了每个节点出度指向的节点

解题思路

需要首先构造一个邻接表保存节点的出度指向节点,另一个数组用于保存每个节点的入度

这里用数组下标替代了 map 的 key

双层循环去做查找入度为 0 的节点以及更新节点入度的动作

虽然时间复杂度来到了 N2 但却是最好理解的

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] res = new int[numCourses];
        if (numCourses == 0) return res;

        // 记录了每个节点的入度是多少
        int[] inDegree = new int[numCourses];
        // 邻接表记录了每个节点出度指向了哪些节点
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
        for (int[] pair : prerequisites) {
            int to = pair[0];
            int from = pair[1];
            graph.get(from).add(to);
            inDegree[to]++;
        }

        int index = 0;
        for (int j = 0; j < numCourses; j++) {
            for (int i = 0; i < numCourses; i++) {
                if (inDegree[i] == 0) {
                    res[index++] = inDegree[i];
                    inDegree[i] = -1;
                    for (int neighbor : graph.get(i)) inDegree[neighbor]--;
                }
            }
        }
        if (index == numCourses) return res;
        else return new int[0];
    }

如果需要优化时间复杂度到 O(m+n)

用队列来优化这个双层循环,同时不再显式构建邻接表

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] res = new int[numCourses];
        // 记录了每个节点的入度是多少
        int[] inDegree = new int[numCourses];
        for (int[] pair : prerequisites) inDegree[pair[0]]++;

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) if (inDegree[i] == 0) queue.offer(i);

        int index = 0;
        while (!queue.isEmpty()) {
            int curr = queue.poll();
            res[index++] = curr;
            // 更新入度以及队列
            for (int[] pair : prerequisites) {
                int to = pair[0];
                int from = pair[1];
                if (curr == from) {
                    inDegree[to]--;
                    if (inDegree[to] == 0) queue.offer(to);
                }
            }
        }
        if (index == numCourses) return res;
        else return new int[0];
    }

这样可以将时间复杂度降至n+e的同时,将空间复杂度降至 n

posted @ 2022-09-19 15:31  YaosGHC  阅读(52)  评论(0)    收藏  举报