最小生成树,prim算法

Prim算法和Kruskal算法都是用于解决最小生成树问题的经典算法,它们在不同情况下有不同的适用性和特点。

Prim算法:

Prim算法是一种贪心算法,用于构建一个无向图的最小生成树。算法从一个初始节点开始,逐步添加与当前树连接且具有最小权重的边,直到所有节点都被连接。Prim算法的基本思想是从一个起始节点出发,每次选择一个与当前最小生成树相连的节点中,权重最小的边,将这个节点加入最小生成树中,并将其相连的边考虑进来。这样逐步扩展最小生成树,直至所有节点都被包含。

Kruskal算法:

Kruskal算法也是一种贪心算法,用于构建一个无向图的最小生成树。该算法首先将图中的所有边按照权重从小到大进行排序,然后从最小权重边开始,依次将边加入生成树中,但要保证加入边不会形成环。如果加入某条边会导致环的形成,则放弃该边,继续考虑下一条权重较小的边,直到生成树中包含了所有的节点。

区别:

基本思想:Prim算法从一个起始节点开始,逐步添加与当前最小生成树相连的具有最小权重的边。Kruskal算法通过排序边,然后逐个添加边,保证不形成环。

顶点处理:Prim算法在每一步选择中,仅考虑与当前已选择顶点相连的顶点,直接操作顶点Kruskal算法是通过遍历边的方式进行操作,不直接关心顶点。

数据结构:Prim算法通常使用优先队列(最小堆)来维护待选的边,以便每次选择具有最小权重的边。Kruskal算法则通常使用并查集来判断是否会形成环。

性能:在边的数量较少,而顶点的数量较多时,Prim算法通常会更有效。而在边的数量较多,而顶点的数量较少时,Kruskal算法可能更适用。

复杂度:Prim算法的时间复杂度通常在 O(V^2) 到 O(E* log(V)) 之间,取决于实现方式。Kruskal算法的时间复杂度主要由排序边的复杂度决定,通常为 O(E * log(E))。

总的来说,两种算法都能有效地构建最小生成树,但在不同情况下选择合适的算法可以提高效率。

1135. 最低成本联通所有城市

想象一下你是个城市基建规划者,地图上有 n 座城市,它们按以 1 到 n 的次序编号。

给你整数 n 和一个数组 conections,其中 connections[i] = [xi, yi, costi] 表示将城市 xi 和城市 yi 连接所要的costi(连接是双向的)。

返回连接所有城市的最低成本,每对城市之间至少有一条路径。如果无法连接所有 n 个城市,返回 -1

该 最小成本 应该是所用全部连接成本的总和。

在这里插入图片描述
代码实现:

// 定义边
struct Edge{
    int city;
    int cost;
};

// 定义 边的比较方法
// cost值较小的Edge对象具有更高的优先级,
// 因为EdgeComparator在a的cost大于b的cost时返回true,
// 这意味着在优先级队列中,a应该在b之后。
// 所以,这个优先级队列是一个最小堆(min heap),即队列顶部总是cost最小的Edge对象
struct EdgeComparator{
    bool operator()(const Edge& a,const Edge& b){
        return a.cost>b.cost;
    }
};

class Solution {
public:
    int minimumCost(int n, vector<vector<int>>& connections) {
        // 连接所有点需要的cost
        int cost = 0;
        vector<vector<Edge>> edges(n+1);
        // 定义访问数组
        vector<bool> visited(n+1,false);
        //  定义优先队列, cost小的排在前面
        priority_queue<Edge, vector<Edge>, EdgeComparator> minHeap;

        visited[1] = true;// 从城市1开始

        // 建立Edge二维数组
        // 每个点会对应一个list,每个list中存储:和这个点相连的城市以及到相连城市的距离
        for(const auto& conn:connections){
            // 是双向的
            edges[conn[0]].push_back(Edge{conn[1],conn[2]});
            edges[conn[1]].push_back(Edge{conn[0],conn[2]});
        }
        // 先把和1点相连的Edge进行排序,放入优先队列
        for(const Edge& edge:edges[1]){
            minHeap.push(edge);
        }
        // 连接点的数量,初始为1
        int count = 1;
        while(!minHeap.empty()){
            Edge e = minHeap.top();
            minHeap.pop();

            if(visited[e.city]){
                // 如果已经访问过某一点了,则直接进入下一次循环
                continue;
            }

            // 如果没有访问过,就设置为true
            visited[e.city] = true;

            // 再把和这个点相连的Edge push进优先队列
            for(const Edge& edge:edges[e.city]){
                minHeap.push(edge);
            }
            cost += e.cost;
            // 连接点的数量+1
            count++;

            if(count == n){
                return cost;
            }
        }
        return -1;
    }
};

1584. 连接所有点的最小费用

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

在这里插入图片描述

完全仿照上面一题的代码,写出了这题的代码,唯二的区别在于,需要自己额外计算一下每个点之间的距离,并且不满足条件时返回0:

// 定义结构体Edge
struct Edge{
    int city;
    int cost;
};

struct EdgeComparator{
    bool operator()(const Edge& a,const Edge& b){
        return a.cost>b.cost;
    }
};

// 计算曼哈顿距离
int compute_Manhattan(vector<vector<int>>& points,int p1,int p2){
    return abs(points[p1][0]-points[p2][0]) + abs(points[p1][1]-points[p2][1]);
}

class Solution {
public:
    int minCostConnectPoints(vector<vector<int>>& points) {
       int cost = 0;
       int n = points.size();// 点的个数
       vector<vector<Edge>> edges(n);
       priority_queue<Edge, vector<Edge>, EdgeComparator> minHeap;
       vector<bool> visited(n,false); // 访问数组

       visited[0] = true; // 从0点开始search

       int count = 1; // 已经访问到了的点(已经相连的点)

       // 建立了 邻接表
       for(int i = 0;i<n-1;i++){
           for(int j = i+1;j<n;j++){
               // 两点间的曼哈顿距离
               int distance = compute_Manhattan(points,i,j);
               edges[i].push_back(Edge{j,distance});
               edges[j].push_back(Edge{i,distance});
           }
       }

        // 把和0点相关的点push进最小堆
        for(const Edge& edge:edges[0]){
            minHeap.push(edge);
        }

        while(!minHeap.empty()){
            Edge e = minHeap.top();
            minHeap.pop();

            if(visited[e.city]){
                // 如果已经访问过该点,则进入下一次循环
                continue;
            }
            visited[e.city] = true; // 标记访问过该点

            // 再把和该点相连的点push 进 minHeap
            for(const Edge& edge:edges[e.city]){
                minHeap.push(edge);
            }

            cost += e.cost; // 加上和这个点相连的cost
            count++;

            if(count == n){
                // 如果n个点都相连了,返回cost即可
                return cost;
            }
        }
        return 0;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值