IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 1584. 连接所有点的最小费用 (Prim算法和Kruskal算法学习) -> 正文阅读

[数据结构与算法]1584. 连接所有点的最小费用 (Prim算法和Kruskal算法学习)

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。
连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。
请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1:

在这里插入图片描述

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20

解释:
在这里插入图片描述

我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。

示例 2:

输入:points = [[3,12],[-2,5],[-4,1]]
输出:18

示例 3:

输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4

示例 4:

输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000

示例 5:

输入:points = [[0,0]]
输出:0

提示:

1 <= points.length <= 1000
-106 <= xi, yi <= 106
所有点 (xi, yi) 两两不同。


解题思路

如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。 生成树是连通图的包含图中的所有顶点的极小连通子图。 图的生成树不惟一。 从不同的顶点出发进行遍历,可以得到不同的生成树。

求连接所有点得到最小总费用,即求最小生成树:一副连通加权无向图中一棵权值最小的生成树。

最小生成树的两种经典算法:Prim算法Kruskal算法

一、Prim算法

算法描述
  1. 输入:一个加权连通图,其中顶点集合为V,边集合为E;
  2. 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
  3. 重复下列操作,直到Vnew = V:
    a. 在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
    b. 将v加入集合Vnew中,将<u, v>边加入集合Enew中;
  4. 输出:使用集合Vnew和Enew来描述所得到的最小生成树。

就是不断从新的顶点集合(初始只有一个顶点)中选取与其相邻不在新顶点集合中的顶点的所有相连边中权值最短的边加入到新边集合中,该顶点加入新顶点集合。这样得到的新顶点集合和新边集合就是最小生成树。

代码

  • 数据结构
    • 集合V:最开始的所有顶点集合
    • 集合Vnew:存放已经加入到最小生成树的顶点
    • 一维数组lowcost:记录V中所有顶点离最小生成树Vnew的最短距离(已加入最小生成树的顶点则记为-1
    • 一维数组visit:记录v中顶点的访问情况(1已加入最小生成树)
    • 集合Enew:只是求最小权值的话就无需记录边了
  • 步骤
    • V中随机选择一个顶点加入Vnew,更新lowcostvisit
    • 重复以下步骤直到所有顶点的visit置为-1
      • 遍历lowcost,寻找最小值,将对应顶点加入Vnew,更新该顶点的visit-1,更新所有V中未加入最小生成树的顶点的lowcost

在这里插入图片描述

  • code1(通过样例65/72:超时)//待分析
    #include "bits/stdc++.h"
    using namespace std;
    
    const int maxn = 0x7f7f7f;
    
    class Solution {
    public:
        int ManhattanDistance(int x, int y) {
            return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
        }
    
        int Prim(int start) {
            vector<int> vNew = {start};                                  // 最小生成树顶点集合
            vector<int> lowCost(n, maxn);                                // 记录还未加入最小生成树的顶点到已加入最小生成树中顶点的距离
            lowCost[start] = -1;                                         // 已加入最小生成树的顶点,cost置为-1
            int result = 0;                                              // 记录最小生成树权值和
            
            while (1) {
                int curMinCost = maxn;                                   // 记录当前lowCost中的最小cost
                int curPoint = -1;                                       // 记录即将加入最小生成树的顶点                 
                for (int i = 0; i < vNew.size(); ++i) {
                    // 更新lowCost
                    for (int j = 0; j < n; ++j) {
                        lowCost[j] = min(lowCost[j], ManhattanDistance(vNew[i], j));
                        // 获取lowCost最小值
                        if (lowCost[j] != -1 && lowCost[j] < curMinCost) {
                            curMinCost = lowCost[j];
                            curPoint = j;
                        }
                    }
                }
                // 将curPoint加入最小生成树,更新最小生成树的权值和
                if (curPoint != -1) {
                    vNew.emplace_back(curPoint);                         // 更新最小生成树
                    result += curMinCost;                                // 更新最小生成树权值和
                    lowCost[curPoint] = -1;                              // 更新已加入最小生成树顶点的lowCost
                } else {
                    break;
                }
            }
    
            return result;
        }
    
        int minCostConnectPoints(vector<vector<int>>& points) {
            this->points = points;
            n = points.size();
            return Prim(0);
        }
    
    private:
        vector<vector<int>> points;
        int n;
    };
    
  • code2
    实际只用了lowCost,顶点直接用下标记录,是否已加入生成树可用lowCost值是否为-1判断,因为权值(曼哈顿距离)不会为负值;
    整数最大值使用内置INT_MAX
    #include "bits/stdc++.h"
    using namespace std;
    
    class Solution {
    public:
        // 计算曼哈顿距离
        int ManhattanDistance(int x, int y) {
            return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
        }
    
        // 普利姆算法计算最小生成树
        int Prim(int start) {
            vector<int> lowCost(n, INT_MAX);
            int result = 0;                                // 记录最小生成树权值和
            
            // 加入顶点start,更新lowCost
            lowCost[start] = -1;                          // lowCost=-1记录已加入最小生成树的顶点
            for (int i = 0; i < n; ++i) {
                lowCost[i] = min(lowCost[i], ManhattanDistance(i, start));
            }
    
            // 重复从未加入最小生成树的顶点中选取最小权边,n个顶点只需选取n-1次(除去初始顶点start)
            for (int i = 0; i < n - 1; ++i) {
                int minIndex = -1;
                int minValue = INT_MAX;
                // 遍历查找当前最小权边
                for (int j = 0; j < n; ++j) {
                    if (lowCost[j] == -1) {
                        continue;
                    }
                    // 更新当前最小权边
                    if (lowCost[j] < minValue) {
                        minValue = lowCost[j];
                        minIndex = j;
                    }
                }
    
                // 将minIndex加入最小生成树,并更新最小生成树权值和
                lowCost[minIndex] = -1;
                result += minValue;
    
                // 更新lowCost
                for (int j = 0; j < n; ++j) {
                    lowCost[j] = min(lowCost[j], ManhattanDistance(minIndex, j));
                }
            }
    
            return result;
        }
    
        int minCostConnectPoints(vector<vector<int>>& points) {
            this->points = points;
            n = points.size();
            return Prim(0);
        }
    
    private:
        vector<vector<int>> points;
        int n;
    };
    

二、Kruskal算法

算法描述

Kruskal 算法是一种贪心算法,其思想很简单:

  • 首先对所有的边进行排序,然后从小到大依次遍历每条边,同时判断每条边是否同源(属于同一个联通分量)
  • 如果同源,跳过;
  • 如果不同源,将两个连通分量合并,直到所有顶点属于同一个连通分量

很明显就是并查集

与Prim算法比较:

  • Prim算法是以顶点为基础(每次寻找离Vnew最近的顶点);
  • Kruskal算法是以为基础,每次从边集合中寻找最小的边(不管两个顶点属于V还是Vnew),然后判断该边的两个顶点是否同源(属于同一个连通分量)。

代码

#include "bits/stdc++.h"
using namespace std;

class UnionFind {
public:
    // 构造函数初始化
    UnionFind(int n) {
        pre.resize(n);
        for (int i = 0; i < n; ++i) {
            pre[i] = i; // 初始将自己作为自己的根节点
        }
    }

    // 查找根节点
    int Find(int x) {
        return x == pre[x] ? x : Find(pre[x]);
    }

    // 合并两个节点
    void Union(int x, int y) {
        pre[Find(x)] = Find(y); // 将其中一个的根结点置为另一个的根结点的父结点
    }

private:
    vector<int> pre;
};

struct Eage {
    int start;
    int end;
    int value;
    Eage(int start, int end, int value) : start(start), end(end), value(value) {}
};

static bool Cmp(const Eage& a, const Eage& b) {
    return a.value < b.value;
}

class Solution {
public:
    // 计算曼哈顿距离
    int ManhattanDistance(vector<vector<int>>& points, int x, int y) {
        return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
    }

    int minCostConnectPoints(vector<vector<int>>& points) {
        int n = points.size();
        vector<Eage> eages;
        int result = 0;
        
        // 重建数据结构
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) { // 不要重复加入边,例如(i,j)和(j,i)
                eages.emplace_back(Eage(i, j, ManhattanDistance(points, i, j)));
            }
        }

        // 排序
        sort(eages.begin(), eages.end(), Cmp);

        // 依次选择最小权边
        UnionFind uf(n); // note:是顶点数不是边数
        int m = eages.size();
        int curLen = 1;
        for (const auto& eage : eages) {
            // 该边的起点和终点不在同一个联通分量,即可加入最小生成树
            if (uf.Find(eage.start) != uf.Find(eage.end)) {
                result += eage.value;                                // 加入最小生成树
                uf.Union(eage.start, eage.end);                      // 合并成一个联通分量
                ++curLen;
                // n个节点均加入最小生成树即可
                if (curLen == n) {
                    break;
                }
            }
        }

        return result;
    }
};

参考:

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:27:24  更:2021-12-26 22:29:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 17:31:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码