LeetCode 【算法专栏】 【图】

有关BFS和DFS

下面考虑了非连通图的情况,如果图是连通图,则只需从源点s出发,就可以遍历完图中的所有点了。有关于BFS和DFS的时间复杂度,对于邻接矩阵实现和邻接表的不同实现方式是不同的。对于BFS和DFS,采用邻接表:O(V + E), 邻接矩阵O(V^2)。区别在于,对于v的每个邻接点进行遍历的时候,邻接表只需访问v对应的链表就行,链表中的边的数目对于无向图来说就是2E。而邻接矩阵,每次都需要遍历一遍顶点的数目。具体分析,请参考书籍。

void MatrixGraph::Bfs() {
	for (int i = 0; i < vertexNum; i++) {
		visited[i] = false;
		path[i] = -1;
	}
	for (int i = 0; i < vertexNum; i++) {
		if (!visited[i]) {
			BfsStartNode(i);
		}
	}
}

void MatrixGraph::BfsStartNode(int s) {
	visited[s] = true;                    //先访问该节点,再将该节点加入队列
	queue<int> que;
	que.push(s);
	while (!que.empty()) {
		int v = que.front();
		que.pop();
		for (auto w : Adj(v)) {
			if (!visited[w]) {
				visited[w] = true;
				path[w] = v;
				que.push(w);
			}
		}
	}
}

void MatrixGraph::Dfs() {
	for (int i = 0; i < vertexNum; i++) {
		visited[i] = false;
		path[i] = -1;
	}
	for (int i = 0; i < vertexNum; i++) {
		if (!visited[i]) {
			DfsStartNode(i);
		}
	}
}

void MatrixGraph::DfsStartNode(int v) {
	visited[v] = true;
	for (auto w : Adj(v)) {
		if (!visited[w]) {
			DfsStartNode(w);
		}
	}
}

求有权图的单源最短路径算法(Dijkstra算法)

令集合S = {源点s + 已经确定了的到源点s最短路径的顶点Vi}

对任一未收录进入S中的顶点v,定义dist[v]为源点s到v的最短路径的长度,该最短路径所经过的点都是在集合S中的点。

即路径{ s ----- (vi 属于 S) ----- v}的最小长度。

若路径是按照递增的顺序生成的,则

1、真正的最短路径必须只经过S中的顶点。

假设下一次将v收入集合时,从集合S到v这个路径上还存在另外一个点w。这个w是在集合S以外的。那从集合S到v,一定要先从S到w,再从w到v。这时就有矛盾,s—w的距离显然小于s—v的距离。而v是下一个马上被收入集合中的顶点,而路径是按照递增序生成的,凡是比v小的点,在v之前就已经收录于集合S中了。w—s的距离小于v—s的距离,则w肯定已经在集合S中了。

2、每次从未收录的顶点中选一个dist最小的收录(贪心),但是将v收入S之中,它可能会影响其他顶点到源点s的最小长度。

3、增加一个v进入S,可能影响另一个w的dist的值,把v加入,使得s—w的dist的值更小,则v一定就在s—w的路径上。v不仅在w的路径上,并且从v到w,必定存在一条直接的边。(w一定是v的邻接点)。v加入集合后,仅能影响的就是它的邻接点。

void Dijkstra(Vertex s) {
	初始化dist存储从源点s到w最短距离dist[w]
	dist[s] = 0,源点s的邻接点的dist的值为其边的值
	初始化path全部为-1,刚开始没有最短路径
	collected数组表示已经收录于集合S中的顶点,初始化全部为false
	while (1) {
		v = 未收录顶点中dist值最小者
		if (这样的v不存在,所有的顶点已经收录于集合S中) {
			break;
		}
		collected[v] = true;                    //标记将v收录于集合S中
		for (v的每个邻接点w) {                   //update operation
		if (collected[w] == false) {
			if (dist[v] + E<v, w> < dist[w]) {
				dist[w] = dist[v] + E<v, w>;
				path[w] = v;
			}
		}
	}
}

该算法的时间复杂度,如果是直接扫描所有未收录的顶点,判断其dist的最小的顶点,则为
O(V^2 + E)。while循环为V次,每次扫描一遍顶点也需要V次,for循环对v的每个邻接点w的处理,总共相当于遍历了一遍图中的所有的边E。

若是将dist的值存于最小堆,则获取未收录顶点中dist值最小者为O(logV),在更新dist值的时候也需要O(logV)的时间复杂度。总的时间复杂度为O(VlogV + ElogV),当为稀疏图时,近似于O(VlogV)。如果对于E不是V^2的数量级,而是V的数量级,这种方式效果要好一些。

void MatrixGraph::ShortestPathDijkstra(int s) {
	vector<int> dist;        //该数组存储从源点s到w的最短距离, dist[s] = 0;
	vector<int> path;        //该数组储存从源点s到w的路径上经过的点, s-->w path[w] = s
	vector<bool> collected;  //该数组表示当前结点是否已经收录于最短路径集合中了
	for (int i = 0; i < vertexNum; i++) {
		dist.push_back(65535);
		path.push_back(-1);
		collected.push_back(false);
	}
	dist[s] = 0;
	for (auto w : Adj(s)) {
		dist[w] = ptrGraph[s][w];
	}
	while (1) {
		int minValue = 65535;
		int v;
		for (int j = 0; j < vertexNum; j++) {
			if (!collected[j] && dist[j] < minValue) {
				minValue = dist[j];
				v = j;
			}
		}
		if (minValue == 65535) {
			break;
		}
		collected[v] = true;                           //update operation
		for (auto w : Adj(v)) {
			if (collected[w] == false) {
				if (dist[v] + ptrGraph[v][w] < dist[w]) {
					dist[w] = dist[v] + ptrGraph[v][w];
					path[w] = v;
				}
			}
		}
	}
}

求有权图的多源最短路径算法(Floyd算法)

方法一:直接将单源最短路径算法调用V遍

T = O(V^3 + E*V),方法一对稀疏图效果好

方法二:Floyd算法

D^(k)[i][j] = 路径{ i — { l <= k } — j }的最小长度

D^0 D^1 … D^(v-1)[i][j]即给出了i到j的真正最短距离

最初的D^-1是什么

当D的(k-1)次方已经完成递推到D^K时:
或者k不属于最短路径{ i — { l <= k } — j },则D的(k-1)次方等于D的k次方
或者k属于最短路径{ i — { l <= k } — j },则该路径必定由两段最短路径组成:
D^(K)[i][j] = D^(k-1)[i][k] + D^(k-1)[k][j]

void Floyd() {
	for(i = 0; i < N; i++)
		for(j = 0; j < N; j++)
		{
			D[i][j] = G[i][j];
			path[i][j] = -1;
		}
	for(k = 0; k < N; k++)
		for(i = 0; i < N; i++)
			for(j = 0; j < N; j++)
			 	if(D[i][k] + D[k][j] < D[i][j])
			 	{
			 		D[i][j] = D[i][k] + D[k][j];
			 		path[i][j] = k;
			 	}
}
void Print(int i, int j)
{
	if(i == j)  return 0;
	Print(i, path[i][j]);     // i --- k
	cout << path[i][j] << endl;
	Print(path[i][j], j);     // k --- j
}

最小生成树—Prim算法

void Prim () {
	MST = {s};
	while (true) {
        v = 未收录的顶点中cost最小者,即距离现有的最小生成树cost最小的那个顶点
        if (这样的v不存在) break;    // 1、顶点全部被收录 2、所有没收录的顶点cost都是无穷大,表明剩下的顶点到最小生成树没有边,图不连通
        将顶点v收录进入MST中
        for (v的每个邻接点w) {
            if (w未被收录) {
                if (cost(v,w) < lowcost[w]) {
                    lowcost[w] = cost(v,w);     //更新未收录集合中和v相连的顶点w到MST的最小代价
                    parent[w] = v;
                }
            }
        }
	}
    if (MST集合中收录的顶点数目不到v个) ERROR(图不连通);
}
//lowcost数组存储的是一个顶点v到最小生成树集MST中所有顶点的代价最小的值,初始化为cost(s,v)或者正无穷
void Prim() {
    vector<int> lowcost(vertexnum, INT_MAX);   
    vector<int> path(vertexnum, -1);             // s->w  ---- path[w] = s
    vector<bool> collected(vertexnum, false);    //将图中的顶点分为了两个集合,collected[i] == true, 表示该顶点已经收录于MST中
    lowcost[s] = -1;    
    for (auto w : adj(s)) lowcost[w] = g[s][w];
    while (1) {
        int min = INT_MAX;
        int v;
        for (int j = 0; j < vertexnum; j++) {
            if (!collected[j] && lowcost[j] < min) {
                min = lowcost[j];
                v = j;
            }
        }
        // 1、顶点全部被收录 2、所有没收录的顶点cost都是无穷大,表明剩下的顶点到最小生成树没有边,图不连通
        if (min == INT_MAX) break; 
        collected[v] = true;
        for (auto w : adj(v)) {
            if (!collected[w]) {
                if (g[v][w] < lowcost[w]) {
                    lowcost[w] = g[v][w];
                    path[w] = v;
                }
            }
        }
    }
    for (int k = 0; k < vertexnum; k++) {         //MST集合中收录的顶点不到verternum个
        if (collected[k] == false) {
            //error
        }
    }
}

leetcde 797 所有可能的路径

class Solution {
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        int n = graph.length;
        boolean[] visited = new boolean[n];
        int n1 = 0;
        int n2 = n - 1;
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        visited[n1] = true;
        path.add(n1);
        dfs(n1, n2, graph, visited, path, res);
        return res;
    }
    
    public void dfs(int n1, int n2, int[][] graph, boolean[] visited, List<Integer> path, List<List<Integer>> res) {
        if (n1 == n2) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int j = 0; j < graph[n1].length; j++) {
            int v = graph[n1][j];
            if (visited[v]) continue;
            visited[v] = true;
            path.add(v);
            dfs(v, n2, graph, visited, path, res);
            visited[v] = false;
            path.remove(path.size()-1);
        }
    }
}

为什么不需要 visited 数组?
有向无环图(DAG)的特性:
题目中给出的图是一个 有向无环图(DAG),即图中不存在环路。
由于没有环路,从起点到终点的路径不会重复访问同一个节点。
DFS 的特性:
在 DFS 遍历过程中,路径是从起点到当前节点的 单向路径,不会重复访问已经访问过的节点。
每次递归调用时,路径会自然地向更深层次扩展,不会回到已经访问过的节点。
如果图中有环,需要 visited 数组吗?
如果图中有环,则需要使用 visited 数组来避免重复访问同一个节点,否则 DFS 会陷入无限循环。

class Solution {
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        List<List<Integer>> result = new ArrayList<>(); // 存储所有路径
        List<Integer> path = new ArrayList<>(); // 当前路径
        path.add(0); // 起点是 0
        dfs(graph, 0, path, result); // 从起点开始 DFS
        return result;
    }

    private void dfs(int[][] graph, int node, List<Integer> path, List<List<Integer>> result) {
        // 如果当前节点是终点,将路径添加到结果集中
        if (node == graph.length - 1) {
            result.add(new ArrayList<>(path));
            return;
        }

        // 遍历当前节点的所有邻居
        for (int neighbor : graph[node]) {
            path.add(neighbor); // 将邻居加入路径
            dfs(graph, neighbor, path, result); // 递归搜索
            path.remove(path.size() - 1); // 回溯,移除邻居
        }
    }
}

KamaCoder 98 所有可达路径

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static List<List<Integer>> result = new ArrayList<>();
    public static List<Integer> path = new ArrayList<>();
    public static boolean[] visited;

    public static void DFS(int[][] graph, int s0, int s1) {
        if (s0 == s1) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 1; i < graph.length; i++) {
            if (graph[s0][i] == 1 && visited[i] == false) {
                visited[i] = true;
                path.add(i);
                DFS(graph, i, s1);
                path.remove(path.size() - 1);
                visited[i] = false;
            }
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] graph = new int[n+1][n+1];
        for (int i = 0; i < m; i++) {
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            graph[s][t] = 1;
        }
        visited = new boolean[n+1];
        for (int i = 0; i < n + 1; i++) {
            visited[i] = false;
        }
        int s0 = 1;
        int s1 = n;
        path.add(s0);
        visited[s0] = true;
        DFS(graph, s0, s1);
        // 输出结果
        if (result.isEmpty()) {
            System.out.println(-1);
        }
        for (List<Integer> pa : result) {
            for (int i = 0; i < pa.size() - 1; i++) {
                System.out.print(pa.get(i) + " ");
            }
            System.out.println(pa.get(pa.size() - 1));
        }
    }
}
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static List<List<Integer>> result = new ArrayList<>(); // 收集符合条件的路径
    static List<Integer> path = new ArrayList<>(); // 1节点到终点的路径

    public static void dfs(List<LinkedList<Integer>> graph, int x, int n) {
        if (x == n) { // 找到符合条件的一条路径
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i : graph.get(x)) { // 找到 x指向的节点
            path.add(i); // 遍历到的节点加入到路径中来
            dfs(graph, i, n); // 进入下一层递归
            path.remove(path.size() - 1); // 回溯,撤销本节点
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();

        // 节点编号从1到n,所以申请 n+1 这么大的数组
        List<LinkedList<Integer>> graph = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            graph.add(new LinkedList<>());
        }

        while (m-- > 0) {
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            // 使用邻接表表示 s -> t 是相连的
            graph.get(s).add(t);
        }

        path.add(1); // 无论什么路径已经是从1节点出发
        dfs(graph, 1, n); // 开始遍历

        // 输出结果
        if (result.isEmpty()) System.out.println(-1);
        for (List<Integer> pa : result) {
            for (int i = 0; i < pa.size() - 1; i++) {
                System.out.print(pa.get(i) + " ");
            }
            System.out.println(pa.get(pa.size() - 1));
        }
    }
}

leetcode 1584 连接所有点的最小费用(prim+priority_queue)

prim

class Solution {
    int minSpanTreePrim(vector<vector<int>>& points, int s) {
        int n = points.size();
        int result = 0;
        vector<vector<int>> g(n, vector<int>(n, 0));
        //将points转换成邻接矩阵
        for (int i = 0; i < n; i++) {
            for (int j = i+1; j < n; j++) {
                int cost = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
                g[i][j] = cost;
                g[j][i] = cost;
            }
        }
        vector<int>  lowcost(n, INT_MAX);
        vector<bool> collected(n, false);
        vector<int>  path(n, s);
        collected[s] = true;
        for (int j = 0; j < n; j++) {
            if (j == s) continue;
            lowcost[j] = g[s][j];
        }
        int flag = 1;
        while (true) {
            int min = INT_MAX;
            int v;
            for (int i = 0; i < n; i++) {
                if (!collected[i] && lowcost[i] < min) {
                    min = lowcost[i];
                    v = i;
                }
            }
            if (min == INT_MAX) break;
            collected[v] = true;
            if (flag) {           //对第一个开始的顶点s单独续接上路径
                path[v] = s;
                flag--;
            }
            result += g[path[v]][v];
            for (int w = 0; w < n; w++) {
                if (w == v) continue;
                if (!collected[w]) {
                    if (g[v][w] < lowcost[w]) {
                        lowcost[w] = g[v][w];
                        path[w] = v;
                    }
                }
            }            
        }
        return result;
    }
public:
    int minCostConnectPoints(vector<vector<int>>& points) {
        return minSpanTreePrim(points, 0);
    }
};

prim + priority_queue

class Solution {
    struct Edge {
        int a;
        int b;
        int weight;
        Edge(int _a, int _b, int _weight) : a(_a), b(_b), weight(_weight) {}
    };
    struct cmp {
        bool operator()(const Edge& e1, const Edge& e2) {
            return e1.weight > e2.weight;   //按照value从小到大排列
        }
    }; 
    int minSpanTreePrim(vector<vector<int>>& points, int s) {
        int n = points.size();      // vertex number
        int result = 0;
        // 将points转化成邻接表
        vector<vector<int>> g(n);
        for (int i = 0; i < n; i++) {
            for (int j = i+1; j < n; j++) {
                g[i].push_back(j);
                g[j].push_back(i);
            }
        }
        //记录顶点i到最小生成树的最近距离
        vector<int> lowcost(n, INT_MAX);
        //记录顶点i是否加入了最小生成树中
        vector<bool> collected(n, false);
        priority_queue<Edge, vector<Edge>, cmp> pq;
        pq.push(Edge(s,s,0));

        while (!pq.empty()) {
            Edge e = pq.top();
            pq.pop();
            if (collected[e.b]) continue; //如果该顶点已经在MinSpanTree集合了continue
            collected[e.b] = true;        //否则将该顶点收录进入MinSpanTree集合,并且将该顶点和它相连的上一个顶点所组成的边权值加入result
            result += e.weight;
            for (int k = 0; k < g[e.b].size(); k++) {  //处理加入MinSpanTree顶点的邻接顶点
                int j = g[e.b][k];
                int w = abs(points[e.b][0] - points[j][0]) + abs(points[e.b][1] - points[j][1]);
                if (!collected[j] && lowcost[j] > w) {
                    lowcost[j] = w;
                    pq.push(Edge(e.b, j, w));
                }
            }
        }
        return result;        
    }
public:
    int minCostConnectPoints(vector<vector<int>>& points) {
        return minSpanTreePrim(points, 0);   //start from vertex index 0
    }
};

Kruskal算法实现思想(并查集)

Kruskal算法实现思想(并查集)
实现思想:将各边按代价值排序,共执行E轮,每轮判断两个顶点是否属于同一个集合,需要O(logE),总时间复杂度O(ElogE)。
1、初始化并查集,按权值递增次序处理各条边
2、通过Find确定一条边所连接的两个顶点是否属于同一个集合,如果不属于同一个集合,则将这条边加入生成树,并将这两个顶点所属集合Union

typedef struct {
    int a;
    int b;
    int weight;
}Edge;
void Initial(vector<int> s) {
    for (int i = 0; i < s.size(); i++) {
        s[i] = -1;
    }
}
int Find(vector<int> s, int x) {
    while (s[x] >= 0) {
        x = s[x];
    }
    return x;
}
//用根节点的绝对值表示树的结点总数
//1、用根节点的绝对值表示树的结点总数
//2、Union操作让小树合并到大树
void Union(vector<int> s, int root1, int root2) {
    if (root1 == root2) return;
    if (s[root1] < s[root2]) {
        s[root1] += s[root2];
        s[root2] = root1; 
    }
    else {
        s[root2] += s[root1];
        s[root1] = root2;
    }
}
void Kruskal(MGraph G, vector<Edge> vec) {
    int n = vertexnum;
    int s[n];
    for (int i = 0; i < n; i++) {
        s[i] = -1;
    }
    Edge edges[Edgenum];
    sort(edges);
    for (int i = 0; i < Edgenum; i++) {
        int roota = Find(s, edges[i].a);
        int rootb = Find(s, edges[i].b);
        if (roota != rootb) {
            vec.push(edges[i]);
            Union(s, roota, rootb);
        }
    }
}
    int Find(vector<int>& s, int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int t = s[x];
            s[x] = root;
            x = t;
        }
        return root;
    }
    void Union(vector<int>& s, int a, int b) {
        int roota = Find(s, a);
        int rootb = Find(s, b);
        if (roota == rootb) return;
        if (s[roota] <= s[rootb]) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        }
        else {
            s[rootb] += s[roota];
            s[roota] = rootb;
        }
    }
    bool IsConnected(vector<int>& s, int a, int b) {
        return Find(s, a) == Find(s, b);
    }

leetcode 107 寻找存在的路径


import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        UF uf = new UF();
        uf.init(n+1);
        for (int i = 0; i < m; i++) {
            int s1 = scanner.nextInt();
            int s2 = scanner.nextInt();
            uf.union(s1, s2);
        }
        int s = scanner.nextInt();
        int d = scanner.nextInt();
        if (uf.isconnected(s,d)) {
            System.out.println(1);
        } else {
            System.out.println(0);
        }
    }
}

class UF {
    private int[] s;
    public void init(int n) {
        s = new int[n];
        for (int i = 0; i < s.length; i++) {
            s[i] = -1;
        }
    }
    public int find(int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int tmp = s[x];
            s[x] = root;
            x = tmp;
        }
        return root;
    }

    public void union(int a, int b) {
        int roota = find(a);
        int rootb = find(b);
        if (roota == rootb) {
            return;
        }
        if (s[roota] <= s[rootb]) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        } else {
            s[rootb] += s[roota];
            s[roota] = rootb;
        }
    }

    public boolean isconnected(int a, int b) {
        return find(a) == find(b);
    }
}

leetcode 1584 连接所有点的最小费用(Kruskal UnionFind)

class Solution {
    struct Edge {
        int a;
        int b;
        int weight;
        Edge(int _a, int _b, int _weight) : a(_a), b(_b), weight(_weight) {}
    };
    static bool cmp(const Edge& e1, const Edge& e2) {
        return e1.weight < e2.weight;
    }
    //Find优化:查询路径压缩
    int Find(vector<int>& s, int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int t = s[x];
            s[x] = root;
            x = t;
        }
        return root;
    }
    //Union优化:用根节点的绝对值表示树的结点总数,让小树合并到大树
    void Union(vector<int>& s, int roota, int rootb) {
        if (roota == rootb) return;
        if (s[roota] <= s[rootb]) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        }
        else
        {
            s[rootb] += s[roota];
            s[roota] = rootb;
        }
    }
    int minSpanTreeKruskal(vector<vector<int>>& points) {
        int result = 0;
        int n = points.size();
        vector<Edge> edges;
        vector<int> s(n, -1);             // initial find union 
        for (int i = 0; i < n; i++) {
            for (int j = i+1; j < n; j++) {
                int cost = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
                edges.push_back(Edge(i, j, cost));
            }
        }
        sort(edges.begin(), edges.end(), cmp);
        for (int i = 0; i < edges.size(); i++) {
            int a = edges[i].a;
            int b = edges[i].b;
            int roota = Find(s, a);
            int rootb = Find(s, b);
            if (roota != rootb) {
                result += edges[i].weight;
                Union(s, roota, rootb);
            }
        }
        return result;
    }
public:
    int minCostConnectPoints(vector<vector<int>>& points) {
        return minSpanTreeKruskal(points);
    }
};

leetcode 207 课程表(拓扑排序)

class Solution {
public:
    bool Topologicalsort(vector<vector<int>>& g, int n) {
        vector<int> indegree(n, -1);
        for (int j = 0; j < n; j++) {
            int cnt = 0;
            for (int i = 0; i < n; i++) {
                if (g[i][j] == 1) {
                    cnt++;
                }
            }
            indegree[j] = cnt;
        }
        stack<int> sck;
        for (int i = 0; i < n; i++) {
            if (indegree[i] == 0) {
                sck.push(i);
            }
        }
        int cnt = 0;
        while (!sck.empty()) {
            int v = sck.top();
            sck.pop();
            cnt++;
            for (int j = 0; j < n; j++) {
                if (g[v][j] == 0) continue;
                if (!(--indegree[j])) {
                    sck.push(j);
                }
            }
        }
        if (cnt < n) return false;
        else return true;
    }
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        int n = numCourses;
        vector<vector<int>> g(n, vector<int>(n, 0));
        for (int i = 0; i < prerequisites.size(); i++) {
            g[prerequisites[i][1]][prerequisites[i][0]] = 1;     // bi ---> ai
        }
        return Topologicalsort(g, n);
    }
};

leetcode 210 课程表II(拓扑排序)

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer>[] graph = new List[numCourses];
        for (int i = 0; i < numCourses; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            graph[prerequisites[i][1]].add(prerequisites[i][0]);
        }
        int[] indegree = new int[numCourses];
        for (int i = 0; i < prerequisites.length; i++) {
            indegree[prerequisites[i][0]]++;
        }
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < indegree.length; i++) {
            if (indegree[i] == 0) queue.offer(i);
        }
        List<Integer> res = new ArrayList<>();
        int cnt = 0;
        while (!queue.isEmpty()) {
            Integer v = queue.poll();
            res.add(v);
            cnt++;
            for (Integer w : graph[v]) {
                if ((--indegree[w]) == 0) {
                    queue.offer(w);
                }
            }
        }
        if (cnt == numCourses) return res.stream().mapToInt(Integer::intValue).toArray();
        else return new int[0];
    }
}

leetcode 1462 课程表 IV(拓扑排序)

class Solution {
    public List<Boolean> checkIfPrerequisite(int numCourses, int[][] prerequisites, int[][] queries) {
        List<Integer>[] graph = new List[numCourses];
        for (int i = 0; i < numCourses; i++) {
            graph[i] = new ArrayList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            graph[prerequisites[i][0]].add(prerequisites[i][1]);
        }
        int[] indegree = new int[numCourses];
        for (int i = 0; i < prerequisites.length; i++) {
            indegree[prerequisites[i][1]]++;
        }
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < indegree.length; i++) {
            if (indegree[i] == 0) queue.offer(i);
        }

        // 初始化一个二维布尔数组,用于表示课程之间的先修关系
        boolean[][] isPre = new boolean[numCourses][numCourses];
        // 当队列不为空时,循环处理队列中的课程
        while (!queue.isEmpty()) {
            // 从队列中取出一门已经可以学习的课程
            Integer v = queue.poll();
            // 遍历该课程的所有后续课程
            for (Integer w : graph[v]) {
                // 设置课程v是课程w的先修课程
                isPre[v][w] = true;
                // 更新所有课程对课程w的先修关系
                for (int i = 0; i < numCourses; i++) {
                    // 如果课程i是课程v的先修课程,那么课程i也是课程w的先修课程
                    isPre[i][w] = isPre[i][w] || isPre[i][v];
                }
                // 当课程w的所有先修课程都已完成时,将课程w加入队列
                if (--indegree[w] == 0) {
                    queue.offer(w);
                }
            }
        }
        List<Boolean> res = new ArrayList<>();
        for (int[] query : queries) {
            res.add(isPre[query[0]][query[1]]);
        }
        return res;
    }
}

leetcode 133 克隆图 (DFS + DeepCopy + ShadowCopy)

其实就是深拷贝的一个实现,深拷贝就是对于所有的指针成员,不能仅仅是赋值,还有重新分配空间。
深拷贝反应在本题中就是,所有的结点需要重新new出来,而不是直接赋值。
整体的思路依然是dfs,跑遍原图中每个结点,然后根据原结点生成新结点。
要注意的地方就是,因为图存在环,所以要标记访问过的结点,避免重复形成死循环:
如果该结点已经被访问过,则不再遍历该结点,而是直接将指针值赋给自己邻接点数组----浅拷贝;
如果该结点没有被访问过,则继续dfs遍历该结点,并将产生的新结点----深拷贝;

//注意每个节点的值都和它的索引相同
class Solution {
public:
    Node* cloneGraph(Node* node) {
        vector<bool> visited(101, false);
        unordered_map<int, Node*> hashMap;
        if (node == NULL) return node;
        return dfs(hashMap, visited, node);
    }
    Node* dfs(unordered_map<int, Node*>& hashMap, vector<bool>& visited, Node* node) {
        Node* root = new Node(node->val);
        visited[root->val] = true;
        hashMap[root->val] = root;
        for (int i = 0; i < node->neighbors.size(); i++) {
            if (!visited[node->neighbors[i]->val]) {
                root->neighbors.push_back(dfs(hashMap, visited, node->neighbors[i]));    //深拷贝
            }
            else {
                root->neighbors.push_back(hashMap[node->neighbors[i]->val]);    //浅拷贝
            }
        }
        return root;
    }
};
/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> neighbors;
    public Node() {
        val = 0;
        neighbors = new ArrayList<Node>();
    }
    public Node(int _val) {
        val = _val;
        neighbors = new ArrayList<Node>();
    }
    public Node(int _val, ArrayList<Node> _neighbors) {
        val = _val;
        neighbors = _neighbors;
    }
}
*/

class Solution {
    private Map<Node, Node> hashMap = new HashMap<>();
    public Node cloneGraph(Node node) {
        if (node == null) {
            return node;
        }
        // 如果该节点已经被访问过了,则直接从哈希表中取出对应的克隆节点返回
        if (hashMap.containsKey(node)) {
            return hashMap.get(node);
        }
        // 克隆节点,注意到为了深拷贝我们不会克隆它的邻居的列表
        Node cloneNode = new Node(node.val);
        // 哈希表存储
        hashMap.put(node, cloneNode);
        // 遍历该节点的邻居并更新克隆节点的邻居列表
        for (Node neighbor : node.neighbors) {
            cloneNode.neighbors.add(cloneGraph(neighbor));
        }
        return cloneNode;
    }
}
class Solution {
    public Node cloneGraph(Node node) {
        boolean[] visited = new boolean[101];
        if (node == null) return null;
        Map<Integer, Node> hashMap = new HashMap<>();
        Node root = dfs(node, visited, hashMap);
        return root;
    }
    public Node dfs(Node node, boolean[] visited, Map<Integer, Node> hashMap) {
        Node newNode = new Node(node.val);
        visited[node.val] = true;
        hashMap.put(node.val, newNode);
        for (int i = 0; i < node.neighbors.size(); i++) {
            if (!visited[node.neighbors.get(i).val]) {
                // 深拷贝
                newNode.neighbors.add(dfs(node.neighbors.get(i), visited, hashMap));
            } else {
                // 浅拷贝
                int neighVal = node.neighbors.get(i).val;
                Node copyNode = hashMap.get(neighVal);
                newNode.neighbors.add(copyNode);
            }
        }
        return newNode;
    }
}
class Solution {
    public Node cloneGraph(Node node) {
        if (node == null) {
            return node;
        }

        HashMap<Node, Node> visited = new HashMap();

        // 将题目给定的节点添加到队列
        LinkedList<Node> queue = new LinkedList<Node> ();
        queue.add(node);
        // 克隆第一个节点并存储到哈希表中
        visited.put(node, new Node(node.val, new ArrayList()));

        // 广度优先搜索
        while (!queue.isEmpty()) {
            // 取出队列的头节点
            Node n = queue.remove();
            // 遍历该节点的邻居
            for (Node neighbor: n.neighbors) {
                if (!visited.containsKey(neighbor)) {
                    // 如果没有被访问过,就克隆并存储在哈希表中
                    visited.put(neighbor, new Node(neighbor.val, new ArrayList()));
                    // 将邻居节点加入队列中
                    queue.add(neighbor);
                }
                // 更新当前节点的邻居列表
                visited.get(n).neighbors.add(visited.get(neighbor));
            }
        }

        return visited.get(node);
    }
}

leetcode 547 省份数量(UnionFind)


class Solution {
public:
    int Find(vector<int> s, int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int tmp = s[x];
            s[x] = root;
            x = tmp;
        }
        return root;
    }
    void Union(vector<int>& s, int roota, int rootb) {
        if (s[roota] <= s[rootb]) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        }
        else
        {
            s[rootb] += s[roota];
            s[roota] = rootb;            
        }
    }
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n = isConnected.size();
        vector<int> s(n, -1);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (isConnected[i][j] == 1) {
                    int rooti = Find(s, i);
                    int rootj = Find(s, j);
                    if (rooti != rootj) {
                        Union(s, rooti, rootj);
                    }
                }
            }
        }
        int cnt = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] < 0) {
                cnt++;
            }
        }
        return cnt;
    }
};

leetcode 310 最小高度树(BFS DFS Topologicalsort)

class Solution {
public:
	vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
		// 根据边表edges 创建无向邻接表
		vector<vector<int>> g(n);
		for (int i = 0; i < edges.size(); i++) {
			int a = edges[i][0];
			int b = edges[i][1];
			g[a].push_back(b);
			g[b].push_back(a);
		}
		// 从每个顶点依次层序遍历,记录最小层数的顶点
		int minHigh = INT_MAX;
		vector<int> result;
		for (int i = 0; i < n; i++) {
			int high = BFS(g, i);
			if (high > minHigh) continue;
			if (high == minHigh) {
				result.push_back(i);
			}
			else {
				result.clear();
				result.push_back(i);
				minHigh = high;
			}
		}
		return result;
	}
	//BFS广度优先搜索,返回最大深度
	int BFS(const vector<vector<int>>& g, int s) {
		int n = g.size();
		if (s >= n) return -1;
		vector<bool> visited(n, false);
		queue<int> que;
		que.push(s);            //将顶点Push进入队列后,标记visited标志位
		visited[s] = true;
		int high = 0;
		while (!que.empty()) {
			int size = que.size();
			high++;
			for (int i = 0; i < size; i++) {
				int v = que.front();
				que.pop();
				for (auto w : g[v]) {
					if (visited[w]) continue;
					que.push(w);
					visited[w] = true;
				}
			}
		}
		return high;
	}
};
    //递归函数直接传参数进去,递归返回时,下一层的改变不会影响上一层
    //递归函数传递引用,递归返回时,下一层的改变会影响到上一层,如果我们需要回退状态,就不希望下一层的改变影响到上一层的结果
    int DFS(vector<vector<int>>& g, vector<bool> visited, int s) {
        visited[s] = true;
        int ret = 0;
        for (auto w : g[s]) {
            if (!visited[w]) {
                ret = max(ret, DFS(g, visited, w));
            }
        }
        return ret + 1;
    }
class Solution {
public:
    // <int (v1|v2), int maxDepth> 
    // (v1|v2) = v1 * 1e5 + v2
    // v1:start  v2:root
    unordered_map<int, int> memo;

	vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
		// 根据边表edges 创建无向邻接表
		vector<vector<int>> g(n);
		for (int i = 0; i < edges.size(); i++) {
			int a = edges[i][0];
			int b = edges[i][1];
			g[a].push_back(b);
			g[b].push_back(a);
		}
		// 从每个顶点依次层序遍历,记录最小层数的顶点
		int minHigh = INT_MAX;
		vector<int> result;
		for (int i = 0; i < n; i++) {
			int high = DFS(g, i, -1);
			if (high > minHigh) continue;
			if (high == minHigh) {
				result.push_back(i);
			}
			else {
				result.clear();
				result.push_back(i);
				minHigh = high;
			}
		}
		return result;
	}

    // 从start结点开始深度遍历其子树返回最长/深子树的深度,root为避免继续遍历的结点
    int DFS(const vector<vector<int>>& adjList, int start, int root) {
        int key = start * 1e5 + root;
        if (memo.count(key)) return memo[key];
        int ret = 0;
        for (auto adj : adjList[start]) {
            if (adj == root) continue;
            ret = max(ret, DFS(adjList, adj, start));
        }
        memo[key] = ret + 1;
        return ret + 1;
    }
};
class Solution {
public:
	vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
		if (n == 1) return {0};
        // 根据边表edges 创建邻接表和各节点度数
		vector<vector<int>> g(n);
        vector<int> degree(n, 0);
		for (int i = 0; i < edges.size(); i++) {
			int a = edges[i][0];
			int b = edges[i][1];
			g[a].push_back(b);
			g[b].push_back(a);
            degree[a]++;
            degree[b]++;
		}
        //将目前图中的叶子节点压入队列
        queue<int> que;
        vector<int> result;
        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) {
                que.push(i);
            }
        }
        //从最外层向内,每次加入新的一层进入队列,消掉队列的上一层节点
        while (!que.empty()) {
            result.clear();
            int size = que.size();
            for (int i = 0; i < size; i++) {
                int v = que.front();
                que.pop();
                result.push_back(v);
                degree[v]--;           //判断与结点v相连的顶点
                for (auto w : g[v]) {
                    degree[w]--;
                    if (degree[w] == 1) {
                        que.push(w);
                    }
                }            
            }
        }
		return result;
	}
};

leetcode 399 除法求值(BFS DFS Floyd)

class Solution {
public:
    void BFS(vector<vector<pair<int, double>>>& graph, int n, int start, int end, double& wgt) {
        if (start == end) {
            wgt = 1.0;
            return;
        }
        queue<int> que;
        vector<bool> visited(n, false);
        vector<double> weight(n, -1.0);
        que.push(start);
        weight[start] = 1.0;
        visited[start] = true;
        while (!que.empty() && !visited[end]) {
            int v = que.front();
            que.pop();
            for (auto ele : graph[v]) {
                int w = ele.first;
                double val = ele.second;
                if (visited[w]) continue;
                weight[w] = weight[v] * val;
                que.push(w);
                visited[w] = true;
            }
        }
        wgt = weight[end];
        return;
    }
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        //构建字符结点和结点索引编号的映射关系
        int nodeId = 0;
        unordered_map<string, int> hashMap;
        for (int i = 0; i < equations.size(); i++) {
            if (hashMap.find(equations[i][0]) == hashMap.end()) {
                hashMap[equations[i][0]] = nodeId++;
            }   
            if (hashMap.find(equations[i][1]) == hashMap.end()) {
                hashMap[equations[i][1]] = nodeId++;
            }
        }
        //建图,邻接表,因为是无向带权图,每个结点记录它的连接关系的同时,还需要记录它和其他结点权重
        vector<vector<pair<int, double>>> graph(nodeId);
        for (int i = 0; i < equations.size(); i++) {
            int idv = hashMap[equations[i][0]];
            int idw = hashMap[equations[i][1]];
            graph[idv].push_back({idw, values[i]});
            graph[idw].push_back({idv, 1 / values[i]});
        }
        //计算图中存在的两个结点之间的权值
        vector<double> result;
        for (auto query : queries) {
            double answer = -1.0;
            if (hashMap.find(query[0]) != hashMap.end() && hashMap.find(query[1]) != hashMap.end()) {
                int ida = hashMap[query[0]];
                int idb = hashMap[query[1]];
                BFS(graph, nodeId, ida, idb, answer);
            }
            result.push_back(answer);
        }
        return result;
    }
};
class Solution {
public:
    void DFS(vector<vector<pair<int, double>>>& graph, vector<bool> visited, int start, int end, double wgt, double& answer) {
        if (start == end) {
            answer = wgt;
            return;
        }
        visited[start] = true;
        for (auto ele : graph[start]) {
            int v = ele.first;
            double val = ele.second;
            if (visited[v]) continue;
            wgt = wgt * val;
            DFS(graph, visited, v, end, wgt, answer);
            wgt = wgt / val;
        }
    }
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        //构建字符结点和结点索引编号的映射关系
        int nodeId = 0;
        unordered_map<string, int> hashMap;
        for (int i = 0; i < equations.size(); i++) {
            if (hashMap.find(equations[i][0]) == hashMap.end()) {
                hashMap[equations[i][0]] = nodeId++;
            }   
            if (hashMap.find(equations[i][1]) == hashMap.end()) {
                hashMap[equations[i][1]] = nodeId++;
            }
        }
        //建图,邻接表,因为是无向带权图,每个结点记录它的连接关系的同时,还需要记录它和其他结点权重
        vector<vector<pair<int, double>>> graph(nodeId);
        for (int i = 0; i < equations.size(); i++) {
            int idv = hashMap[equations[i][0]];
            int idw = hashMap[equations[i][1]];
            graph[idv].push_back({idw, values[i]});
            graph[idw].push_back({idv, 1 / values[i]});
        }
        //计算图中存在的两个结点之间的权值
        vector<double> result;
        for (auto query : queries) {
            double answer = -1.0;
            if (hashMap.find(query[0]) != hashMap.end() && hashMap.find(query[1]) != hashMap.end()) {
                int ida = hashMap[query[0]];
                int idb = hashMap[query[1]];
                vector<bool> visited(nodeId, false);
                DFS(graph, visited, ida, idb, 1.0 ,answer);
            }
            result.push_back(answer);
        }
        return result;
    }
};
class Solution {
public:
    void floydFunc(vector<vector<double>>& graph, int n) {
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (graph[i][k] > 0 && graph[k][j] > 0) {    //i-->k  k--->j connected
                        graph[i][j] = graph[i][k] * graph[k][j];
                    }
                }
            }
        }
    }
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        //构建字符结点和结点索引编号的映射关系
        int nodeId = 0;
        unordered_map<string, int> hashMap;
        for (int i = 0; i < equations.size(); i++) {
            if (hashMap.find(equations[i][0]) == hashMap.end()) {
                hashMap[equations[i][0]] = nodeId++;
            }   
            if (hashMap.find(equations[i][1]) == hashMap.end()) {
                hashMap[equations[i][1]] = nodeId++;
            }
        }
        //建图,邻接矩阵
        vector<vector<double>> graph(nodeId, vector<double>(nodeId, -1.0));
        for (int i = 0; i < equations.size(); i++) {
            int idv = hashMap[equations[i][0]];
            int idw = hashMap[equations[i][1]];
            graph[idv][idw] = values[i];
            graph[idw][idv] = 1 / values[i];
        }
        floydFunc(graph, nodeId);
        //计算图中存在的两个结点之间的权值
        vector<double> result;
        for (auto query : queries) {
            double answer = -1.0;
            if (hashMap.find(query[0]) != hashMap.end() && hashMap.find(query[1]) != hashMap.end()) {
                int ida = hashMap[query[0]];
                int idb = hashMap[query[1]];
                if (graph[ida][idb] > 0) answer = graph[ida][idb];
            }
            result.push_back(answer);
        }
        return result;
    }
};

leetcode 990 等式方程的可满足性(UnionFind)

class Solution {
public:
    int Find(vector<int>& s, int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int t = s[x];
            s[x] = root;
            x = t;
        }
        return root;
    }
    void Union(vector<int>& s, int roota, int rootb) {
        if (roota <= rootb) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        }
        else {
            s[rootb] += s[roota];
            s[roota] = rootb;
        }
    }
    bool equationsPossible(vector<string>& equations) {
        int n = 26;
        vector<vector<int>> graph(n, vector<int>(n, 0));
        for (auto str : equations) {
            if (str[1] == '=') {
                int v = str[0] - 'a';
                int w = str[3] - 'a';
                graph[v][w] = 1;
                graph[w][v] = 1;
            }
        }
        vector<int> s(n, -1);        
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (graph[i][j] == 1) {
                    int rooti = Find(s, i);
                    int rootj = Find(s, j);
                    if (rooti != rootj) Union(s, rooti, rootj);
                }
            }
        }
        for (auto str : equations) {
            if (str[1] == '!') {
                int a = str[0] - 'a';
                int b = str[3] - 'a';
                int roota = Find(s, a);
                int rootb = Find(s, b);
                if (roota == rootb) {
                    return false;
                }
            }
        }
        return true;
    }
};

leetcode 684 冗余连接(UnionFind)

class Solution {
public:
    int Find(vector<int>& s, int x) {
        int root = x;
        while (s[root] >= 0) {
            root = s[root];
        }
        while (x != root) {
            int t = s[x];
            s[x] = root;
            x = t;
        }
        return root;
    }
    void Union(vector<int>& s, int a, int b) {
        int roota = Find(s, a);
        int rootb = Find(s, b);
        if (roota == rootb) return;
        if (s[roota] <= s[rootb]) {
            s[roota] += s[rootb];
            s[rootb] = roota;
        }
        else {
            s[rootb] += s[roota];
            s[roota] = rootb;
        }
    }
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        vector<vector<int>> result;
        vector<int> s(1001, -1);
        for (auto edge : edges) {
            int v = edge[0];
            int w = edge[1];
            if (Find(s, v) == Find(s, w)) {
                result.push_back(edge);
            }
            else {
                Union(s, v, w);
            }
        }
        return result.back();
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值