图论和并查集代码模板

并查集

并查集结构的定义(路径压缩 + 按秩合并)

class UnionFind{
public:
    vector<int> fa;
    vector<int> height;
    int setCount;//统计合并操作后,剩余的不相交集合个数
    UnionFind(int n){
        setCount = n;
        fa = vector<int>(n, -1);
        height = vector<int>(n, 1);
        for(int i = 0; i < n; i++) fa[i] = i;
    }
    //带路径压缩优化的查找操作
    int find(int x){
        if(x == fa[x]) return x;
        else return fa[x] = find(fa[x]);
    }
    //带按秩求并优化的合并操作
    void merge(int x, int y){//将x所在集合与y所在集合合并成一个集合
        int x_root = find(x), y_root = find(y);
        if(x_root != y_root) {//检查x和y是否已经在一个集合内了,如果在则不用合并了
            //较低的树挂在较高的树上
            if(height[x_root] <= height[y_root]){
                fa[x_root] = y_root;
                if(height[x_root] == height[y_root]) height[x_root] += 1;
            }else if(height[x_root] > height[y_root]){
                fa[y_root] = x_root;
            }
            setCount -= 1;//每合并一次,不相交的集合数量就少1
        }
    }
};

并查集生成过程

在主函数中遍历图的所有边:对边的两个端点执行merge操作即可。

基于bfs的拓扑排序

  • 一个入度数组和一个队列,在建图的过程中顺便计算入度,所有将入度为0的顶点加入队列,然后进行队列不为空的while循环:每次取出一个顶点,更新拓扑序列,将其所有邻边入度减1并判断是否为0,如果为0则加入队列。
  • 所有进入过队列的顶点为满足拓扑排序规则的顶点,这些顶点必然不存在圈,若有剩余顶点,则剩余顶点就构成若干个圈。
		//无权有向图的构建与顶点入度信息统计
        vector<vector<int>> graph(numCourses, vector<int>());
        vector<int> indegree(numCourses, 0);
        for(int i = 0; i < prerequisites.size(); i++){
            int u = prerequisites[i][1], v = prerequisites[i][0];
            graph[u].push_back(v);
            indegree[v] += 1;
        }
        vector<int> ans;//用于保存拓扑排序结果
        queue<int> q;//队列里存放入度为0的顶点,以bfs的方式扩展
        int count = 0;//可以进行拓扑排序的顶点数量,用于判圈
        //初始化队列:找到所有入度为0的顶点,加入队列
        for(int i = 0; i < numCourses; i++){
            if(indegree[i] == 0) q.push(i);
        }
        //利用队列 + bfs扩展的方式进行拓扑排序
        //每次从队列中取出一个入度为0的顶点,加入排序结果,同时将其相邻的顶点入度减1(若变为0则加入队列)
        while(!q.empty()){
            int u = q.front(); q.pop();
            ans.push_back(u);
            count += 1;
            for(int i = 0; i < graph[u].size(); i++){
                int v = graph[u][i];
                if(--indegree[v] == 0){
                    q.push(v);
                }
            }
        }
        vector<int> blank;
        return count == numCourses ? ans : blank; 

无权图的单源最短路

算法思路:准备一个队列、一个visited数组和一个dists距离数组,一个全局的dist值,初始为0。默认源点的dist值为0,并将源点加入队列。然后进行双重while循环的bfs:外层更新当前获取当前层的结点数size(就是队列的大小);内层循环进行size次:每次取出对头顶点并将其未访问过的所有邻接点进入队列,并更新邻接点的dist值为全局的dist + 1,内层结束后全局dist++。.代码较为简单,暂不展示。

单源带权图的最短路

朴素版Dijkstra

  • 准备一个dists数组(初始全为INT_MAX)、一个visted数组(初始全为false)、一个”找出所有未确定距离的顶点中的最小距离“的函数。源点k的dists[k]置0。然后进行满足“存在未确定距离的距离最小顶点”为条件的while循环,拿到这个顶点u,将其距离设置为确定(visited置为true),并尝试松弛其所有未确定距离的邻接点,松弛条件为dists[u] + |u,v| < dists[v],松弛操作为dists[v] = dists[u] + |u,v|。
  • Dijkstra无法负权图的单源最短路,也无法检测负圈。
		vector<bool> visited(n + 1, false);//到源点的最短距离是否确定
        vector<int> dist(n + 1, INT_MAX);//到源点的最短距离
        dist[k] = 0;
        int u = -1;
        while( ( u = getMin(dist, visited, n) ) != -1){//每次寻找距离未确定的最小节点
            visited[u] = true;//则最小节点距离已确定
            for(int j = 0; j < graph[u].size(); j++){//松弛与其相邻而且未确定距离的节点
                int v = graph[u][j][0], weight = graph[u][j][1];
                if(!visited[v] && dist[u] + weight < dist[v]){
                    dist[v] = dist[u] + weight;
                }
            }
        }

最小堆优化版Dijkstra

  • 相比于朴素版无准备需“找出所有未确定距离的顶点中的最小距离的顶点“的函数,取而代之的是一个最小堆,保存的对象为二元组:【此顶点距离源点的距离,此顶点编号 】。循环前需要将源点的二元组加入堆,然后进行满足堆不为空条件的while循环:每次取出堆头顶点,若其距离已经确定,则continue,否则将其visited位置置为true,按相同的方式进行松弛操作,将满足松弛条件的顶点加入堆中。
  • 在稀疏图的前提下效率比朴素版高,但在稠密图的前提下效率反而比朴素版的低。
		vector<bool> visited(n + 1, false);//到源点的最短距离是否确定
        vector<int> dist(n + 1, INT_MAX);//到源点的最短距离
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;//存放距离未确定的节点(小根堆)
        q.push({0, k});
        dist[k] = 0;
        while(!q.empty()){//每次寻找距离未确定的最小节点
            pair<int, int> d_u = q.top(); q.pop();
            int u = d_u.second;
            if(visited[u]) continue;//如果此堆顶节点已确定最短路径,则不做后续操作
            visited[u] = true;//则最小节点距离已确定
            for(int j = 0; j < graph[u].size(); j++){//松弛与其相邻而且未确定距离的节点
                int v = graph[u][j][0], weight = graph[u][j][1];
                if(!visited[v] && dist[u] + weight < dist[v]){
                    dist[v] = dist[u] + weight;
                    q.push({dist[v], v});
                }
            }
        }

Bellman-Ford

  • 只需一个dists数组即可,算法主体为三层循环:对于顶点个数为n的图,对图中所有的边进行n-1次松弛,又称进行n-1次全量松弛。
  • 算法会对尝试对每条边进行松弛,若某次全量松弛未成功对任何一条边进行松弛,则可以提前结束算法。
		vector<int> dist(n + 1, INT_MAX);//到源点的最短距离
        dist[k] = 0;
        for(int i = 1; i <= n - 1; i++){//进行n-1次全量松弛操作
            bool flag = false;
            for(int u = 1; u <= n; u++){//对图中的每一个顶点,尝试松弛其邻边
                for(int j = 0 ;j < graph[u].size(); j++){
                    int v = graph[u][j][0], weight = graph[u][j][1];//获取邻点和邻边权
                    if((long)dist[u] + (long)weight < dist[v]){//决定是否进行松弛操作
                        dist[v] = dist[u] + weight;
                        flag = true;
                    }
                }
            }
            if(!flag) break;//若某次全量松弛未进行任何松弛,则提前结束
        }

SPFA

  • 需要dists数组,一个队列(用于bfs),一个bool数组(用于记录某个顶点是否在队列中)。算法开始前需要进行初始化:初始时讲源点k的dists[k]置为true;将源点加入队列中;将源点k对应的bool类型数组位置置为true,表示源点此时在队列中。
  • 进行队列不为空的while循环,每次循环:取出队头顶点u;标记此顶点已经出队;尝试对u的所有邻接点v进行松弛操作,若v松弛成功,则判断此时v是否已经在队列中,如果不在的话才将v加入队列。
  • SPFA为BF的升级版,依靠队列减少了很多的无效计算。
		vector<int> dist(n + 1, INT_MAX);//到源点的最短距离
        dist[k] = 0;
        queue<int> q; q.push(k);
        vector<bool> inqueue(n + 1, false); inqueue[k] = true;//记录某个顶点是否在队列中
        while(!q.empty()){
            int u = q.front(); q.pop();
            inqueue[u] = false;
            for(int i = 0; i < graph[u].size(); i++){
                int v = graph[u][i][0], weight = graph[u][i][1];
                if((long)dist[u] + (long)weight < dist[v]){
                    dist[v] = dist[u] + weight;
                    if(!inqueue[v]){
                        q.push(v);
                        inqueue[v] = true;
                    }
                }
            }
        }

多源带权图的最短路

Folyed

		//临接矩阵和dist数组共用
        vector<vector<int>> dist(n, vector<int>(n, INT_MAX));
        for(int i = 0; i < n; i++) dist[i][i] = 0;//源点到自己的距离为0
        for(int i = 0; i < edges.size(); i++){
            int u = edges[i][0], v = edges[i][1], weight = edges[i][2];
            dist[v][u] = dist[u][v] = weight;
        }
        //经典三重循环进行松弛操作
        for(int k = 0; k < n; k++){
            for(int i = 0; i < n; i++){
                for(int j = 0; j < n;j ++){
                    int ik = dist[i][k], kj = dist[k][j];
                    if((long)ik + (long)kj < dist[i][j]){
                        dist[i][j] = ik + kj;
                    }
                }
            }
        }

prim求最小生成树

  • 整体代码框架和Dijkstra相似,也分为朴素版和最小堆优化版。这里只给出朴素版代码,最小堆优化版可参考前面的仿写即可。
  • sum变量表示最小生成树的边权和。
		vector<int> dists(n, INT_MAX);
        vector<bool> visited(n, false);
        dists[0] = 0;
        int sum = 0;
        int u = -1;
        while( (u = findMin(dists, visited, n)) != -1 ){
            visited[u] = true;
            sum += dists[u];
            for(int v = 0; v < n; v++){
                int weight = graph[u][v];
                if(!visited[v] && weight < dists[v]) dists[v] = weight;
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值