探索数据结构:图(二)之图的遍历,Kruskal与Prim算法

news2024/11/15 16:16:47


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 图的遍历

图的遍历方式一般分为两种:深度优先遍历广度优先遍历

1.1 深度优先遍历

1.1.1 算法思想

**深度优先遍历(Depth First Search,DFS)**是一种用于遍历或搜索图的算法,过程类似于树的前序遍历。其基本原理为:从图中的某个顶点出发,沿着一条路径尽可能深地探索,直到无法继续前进时,回溯到上一个顶点,再尝试其他未探索的路径。这个过程不断重复,直到所有顶点都被访问到。

具体步骤为:

  1. 首先选择一个起始顶点,并将其标记为已访问。
  2. 从起始顶点出发,选择一个未访问过的邻接顶点,继续进行深度优先遍历。
  3. 如果当前顶点没有未访问的邻接顶点,则回溯到上一个顶点,继续探索上一个顶点的其他未访问邻接顶点。
  4. 重复步骤 2 和 3,直到所有顶点都被访问。

比如我们对下面这幅图进行深度优先遍历:

我们首先从A点开始搜索,沿A,B,E,G一直搜索到G然后回溯到B,接着从B开始沿着C,F,D搜索到D,回溯到F,最后沿着H,I搜索到I,搜索完毕。

1.1.2 具体实现

首先我们通过映射关系找到对应下标,然后定义一个判断该点是否已访问的bool数组,最后进行递归访问。在递归访问中,我们首先访问该数据,然后判断该顶点连接的边是否已访问,如果未访问则递归访问,直到所有边都被访问为止。

void _dfs(int srci, vector<bool>& visited)
{
    //先访问
    cout << _vertexs[srci] << " ";
    //标记已访问
    visited[srci] = true;
    for (int i = 0; i < _vertexs.size(); i++)
    {
        //如果存在边且未被遍历
        if (_matrix[srci][i] != MAX_W && visited[i] == false)
        {
            _dfs(i, visited);
        }
    }
}
//深度优先遍历
void dfs(const V& src)
{
    int srci = getVertexsIndex(src);
    //标记已访问的数组
    vector<bool> visited(_vertexs.size(), false);
    _dfs(srci, visited);
    cout << endl;
}

1.2 广度优先遍历

1.2.1 算法思想

**广度优先遍历(Breadth-First Search,BFS)**是一种用于遍历或搜索图的算法,过程类似于树的层序遍历。其基本原理为:从图中的某个顶点出发,先访问该顶点的所有邻接顶点,再依次访问这些邻接顶点的邻接顶点,如此逐步向外扩展,直到所有顶点都被访问到。

具体步骤为:

  1. 首先选择一个起始顶点,并将其标记为已访问。
  2. 创建一个队列,将起始顶点放入队列中。
  3. 从队列中取出一个顶点,访问该顶点,并将其所有未访问过的邻接顶点放入队列中。
  4. 重复步骤 3,直到队列为空。

比如我们对下面这幅图进行广度优先遍历:

我们首先从A点开始搜索,然后将B,C,D添加到队列,搜索B,C,D过程中再将E,F添加到队列中,接着搜索E,F过程中再将H,G添加到队列,最后搜索完H,G,将I添加到队列,然后搜索完I即搜索完毕。

1.2.2 具体实现

首先我们通过映射关系找到对应下标,然后定义一个判断该点是否已访问的bool数组,将起始顶点对应的下标加入队列。然后访问队列中的元素,访问完的数据我们需要先出队列。最后判断该顶点连接的边是否已访问,如果未访问则继续加入队列,直到所有边都被访问完为止。

//广度优先遍历
void bfs(const V& src)
{
    //获取对应下标
    int srci = getVertexsIndex(src);
    queue<int> q;
    //标记是否被访问的数组
    vector<bool> visited(_vertexs.size(), false);
    q.push(srci);
    visited[srci] = true;
    while (!q.empty())
    {
        int front = q.front();
        q.pop();
        cout << _vertexs[front] << " ";
        for (int i = 0; i < _vertexs.size(); i++)
        {
            //如果存在边,并且没有被访问
            if (_matrix[front][i] != MAX_W && visited[i] == false)
            {
                q.push(i);
                visited[i] = true;
            }
        }
    }
    cout << endl;
}

2. 最小生成树算法

首先我们回忆一下,一个连通图的最小连通子图称为该图的生成树,有 n n n个顶点的连通图的生成树有 n n n个顶点和 n − 1 n - 1 n1条边。最小生成树指的是一个图的生成树中,总权值最小的生成树,并且权值都为正数。

常用的最小生成树算法有两种:Kruskal算法(克鲁斯卡尔算法)Prim算法(普里姆算法),这两者都采用了贪心的策略。

2.1 Kruskal

2.1.1 算法思想

Kruskal算法是一种用于求解无向加权图最小生成树的算法。该算法的目标是在图中找到一个连通子图,它包含了图中的所有顶点,并且边的权值之和最小。其基本思想是按照边的权值从小到大的顺序依次选择边,在选择边的过程中,避免形成回路。
具体步骤如下:

  1. 首先将图中的所有边按照权值从小到大进行排序。
  2. 从权值最小的边开始,依次检查每条边。
    • 如果选择这条边不会形成回路,则将其加入最小生成树中。
    • 如果选择这条边会形成回路,则跳过该边,继续检查下一条边。
  3. 重复步骤 2,直到最小生成树中包含了图中的所有顶点,或者已经检查完了所有的边。
  1. 首先第一步对所有边的权值进行一次排序,然后选出权值最小的边h-g

  1. 继续选择权值最小的边,分别选择了g-fc-ia-bc-f。然后选择i-g时,形成回路,跳过此边。

  1. 继续选择权值最小的边,选择c-d,再选择h-i形成回路。然后跳过,继续选择a-hb-c形成回路。

  1. 最后选择边d-e,接下来无论选择任何边都会形成回路,并且此时边数恰好比节点数少一,选择完毕。

2.1.2 具体实现

每次选取权值最小的边,我们可以使用优先级队列priority_queue实现。判断是否成环,我们可以利用前面我们实现的并查集UnionFindSet实现。并且值得注意的是:向堆中添加边时,无向图只需要添加一次即可。

struct Edge
{
    int _srci; //源顶点的下标
    int _desti; //目标顶点的下标
    W _weight; //权值
    Edge(int srci, int desti, const W& weight)
        :_srci(srci)
        , _desti(desti)
        , _weight(weight)
    {}
    bool operator > (const Edge& edge) const
    {
        return _weight > edge._weight;
    }
};
//Kruskal算法
W Kruskal(Graph<V, W, MAX_W, Direction>& minTree)
{
    //初始化
    int n = _vertexs.size();
    minTree._vertexs = _vertexs;
    minTree._indexMap = _indexMap;
    minTree._matrix.resize(n, vector<W>(n, MAX_W));
    //小堆
    priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
    for (int i = 0; i < n; i++)
    {
        //无向图只需添加一半边
        for (int j = 0; j < i; j++)
        {
            if (_matrix[i][j] != MAX_W)
            {
                minHeap.push(Edge(i, j, _matrix[i][j]));
            }
        }
    }
    UnionFindSet ufs(n);//并查集判断是否成环
    int count = 0;//已选边的数量
    W total = W();//计算选出总权值
    while (!minHeap.empty() && count < n - 1)
    {
        //选最小边
        Edge minEdge = minHeap.top();
        minHeap.pop();
        int srci = minEdge._srci, desti = minEdge._desti;
        W weight = minEdge._weight;
        if (!ufs.inSameSet(srci, desti))
        {
            //添加边
            minTree._matrix[srci][desti] = weight;
            if (Direction == false)
            {
                minTree._matrix[desti][srci] = weight;
            }
            ufs.unionSet(srci, desti);
            //边数++
            count++;
            total += weight;
            cout << "选边: " << _vertexs[srci] << "->" << _vertexs[desti] << ":" << weight << endl;
        }
        else
        {
            cout << "成环: " << _vertexs[srci] << "->" << _vertexs[desti] << ":" << weight << endl;
        }
    }
    //边数与比节点少一
    if (count == n - 1)
    {
        cout << "构成最小生成树" << endl;
        return total;
    }
    else
    {
        cout << "无法构成最小生成树" << endl;
        return W();
    }
}

2.2 Prim

2.2.1 算法思想

Prim算法是一种用于求解无向加权图最小生成树的算法。该算法的目标同样是在图中找到一个连通子图,它包含了图中的所有顶点,并且边的权值之和最小。其基本思想是以一个顶点为起点,逐步向外扩展,每次选择一条连接已加入最小生成树的顶点集和未加入顶点集的权值最小的边。
具体步骤如下:

  1. 任选一个顶点作为起始顶点,将其加入最小生成树中。
  2. 维护一个集合,记录已加入最小生成树的顶点。初始时,该集合只包含起始顶点。
  3. 对于已加入最小生成树的每个顶点,检查其所有邻接边,找到连接已加入顶点集和未加入顶点集的权值最小的边。
  4. 将找到的权值最小的边对应的顶点加入最小生成树中,并更新已加入顶点集。
  5. 重复步骤 3 和 4,直到最小生成树中包含了图中的所有顶点。
  1. 首先从a点开始选择,其中与a点直接相连的边有a-ba-h,选择权值最小的边a-b加入。

  1. 同样以a,b作为起始点选取权值最小的边b-c,同理选出c-i

  1. 同样以a,b,c,i作为起始点选取权值最小的边c-f,同理选出f-g

  1. 最后再依次g-hc-dd-e,选取完毕。

2.2.2 具体实现

为了防止成环,我们采用定义一个bool类似数组

//Prim算法
W Prim(Graph<V, W, MAX_W, Direction>& minTree,const V& start)
{
    //初始化
    int n = _vertexs.size();
    minTree._vertexs = _vertexs;
    minTree._indexMap = _indexMap;
    minTree._matrix.resize(n, vector<W>(n, MAX_W));
    int starti = getVertexsIndex(start);//获取对应下标
    //小堆
    priority_queue<Edge, vector<Edge>, greater<Edge>> minHeap;
    //已访问顶点的集合
    vector<bool> visited(n, false);
    visited[starti] = true;
    for (int i = 0; i < n; i++)
    {
        //将该顶点相连的边全部添加进堆
        if (_matrix[starti][i] != MAX_W)
        {
            minHeap.push(Edge(starti, i, _matrix[starti][i]));
        }
    }
    int count = 0;//已选边的数量
    W total = W();//计算选出总权值
    while (!minHeap.empty() && count < n - 1)
    {
        //选最小边
        Edge minEdge = minHeap.top();
        minHeap.pop();
        int srci = minEdge._srci, desti = minEdge._desti;
        W weight = minEdge._weight;
        //如果该节点没有被添加进集合中
        if (visited[desti] == false)
        {
            for (int i = 0; i < n; i++)
            {
                //将该顶点相连的边全部添加进堆
                if (_matrix[desti][i] != MAX_W && visited[i] == false)
                {
                    minHeap.push(Edge(desti, i, _matrix[desti][i]));
                }
            }
            //添加边
            minTree._matrix[srci][desti] = weight;
            if (Direction == false)
            {
                minTree._matrix[desti][srci] = weight;
            }
            visited[desti] = true;
            //边数++
            count++;
            total += weight;
            cout << "选边: " << _vertexs[srci] << "->" << _vertexs[desti] << ":" << weight << endl;
        }
        else
        {
            cout << "成环: " << _vertexs[srci] << "->" << _vertexs[desti] << ":" << weight << endl;
        }
    }
    //边数与比节点少一
    if (count == n - 1)
    {
        cout << "构成最小生成树" << endl;
        return total;
    }
    else
    {
        cout << "无法构成最小生成树" << endl;
        return W();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2067633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gurobi中引入松弛变量和剩余变量的用法

文章目录 1. 松弛变量&#xff1a;用于“≤”不等式约束数学表达式 2. 剩余变量&#xff1a;用于“≥”不等式约束数学表达式 3. 目标函数中的松弛变量数学表达式 4. Gurobi中的实现对于“≤”不等式的松弛变量&#xff1a;对于“≥”不等式的剩余变量&#xff1a; 5. 总结 在G…

p2p、分布式,区块链笔记: IPFS库Helia的文件系统Unix File System (UnixFS)

Unix File System (UnixFS) Helia中定义一个UnixFS类用于文件处理。The Unix File System (UnixFS) is the data format used to represent files and all their links and metadata in IPFS.。UnixFS中的方法封装了常见的文件系统操作&#xff0c;使得在去中心化文件系统中处…

跨越时代的Zynq PL编程:从xdevcfg到FPGA Manager的进化

引言 在嵌入式系统设计与开发的广阔领域中&#xff0c;Xilinx Zynq平台以其独特的ARM处理器与FPGA可编程逻辑&#xff08;PL&#xff09;的结合&#xff0c;成为了众多创新项目的首选。然而&#xff0c;随着技术的不断进步&#xff0c;Zynq PL的编程方式也经历了从经典到现代的…

【UCB CS61C】Lecture 2 3 - C Basics

目录 C 语言的编译&#xff08;Compilation&#xff09;变量类型&#xff08;Variable Types&#xff09;字符&#xff08;Characters&#xff09; C 语言的类型转换&#xff08;Typecasting&#xff09;类型函数&#xff08;Typed Functions&#xff09; 结构体&#xff08;St…

【C++ Primer Plus习题】3.6

问题: 解答: #include <iostream> using namespace std;int main() {float miles 0;float gallons 0;float gallon 0;cout << "请输入驱车里程(单位为英里):";cin >> miles;cout << "请输入使用的汽油量(单位为加仑):";cin &g…

【数据结构】一篇讲清楚什么是堆? 带图食用超详细~

目录 一、堆的概念 1.堆是一个完全二叉树 2.堆分为大根堆和小根堆。 3.堆与优先级队列的关系 二、堆操作 1.向下调整 2.删除堆顶元素 3.添加新元素 4.构建堆 A&#xff1a;自底向上构建 B&#xff1a;自顶向下构建 C&#xff1a;两种方式对比 三、尝试自己编程实现堆…

redis实战——go-redis的使用与redis基础数据类型的使用场景(二)

一.go-redis操作hash 常用命令&#xff1a; redisClient.HSet("map", "name", "jack") // 批量设置 redisClient.HMSet("map", map[string]interface{}{"a": "b", "c": "d", "e"…

基于springmvc实现文件上传

1.导入jar包 2.修改配置类 在springmvc.xml添加bean <!-- 配置文件上传处理器 --><bean id"multipartResolver" class"org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置在内存中允许的最大文件大小&#x…

趣味算法------柠檬水摊

目录 题目概述&#xff1a; 解题思路&#xff1a; 具体代码&#xff1a; 总结&#xff1a; 题目概述&#xff1a; 在柠檬水摊上&#xff0c;每个柠檬水售价 5 元。客户正在排队向您购买&#xff0c;并且一次订购一份柠檬水。 每位顾客只会购买一份柠檬水&#xff0c;并支付 5…

【python】火灾检测图像处理方法设计(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

深入探讨Java多线程

我的主页&#xff1a;2的n次方_ 1. 多线程的概念 多线程是指在同一个程序中同时执行多个线程的技术。线程是操作系统能够独立调度和执行的最小单位。在Java中&#xff0c;线程由Thread类来表示&#xff0c;所有的线程都是通过这个类或其子类来创建和控制的。通过合理的多线…

解决ERROR: No matching distribution found for imp报错问题

一、问题描述 当我们使用Python3.4及其以上版本运行Python项目时&#xff0c;提示【ModuleNotFoundError: No module named imp】&#xff0c;但是我们使用【pip install imp】命令安装imp时却提示如下错误信息&#xff1a; ERROR: Could not find a version that satisfies t…

深入理解Java代理模式:从静态到动态的实现与应用

1、引言 在Java编程中&#xff0c;代理模式是一种常见的设计模式&#xff0c;用于在不修改原始代码的情况下&#xff0c;为对象添加额外的功能。代理模式有两种主要类型&#xff1a;静态代理和动态代理。本文将全面探讨这两种代理模式&#xff0c;包括它们的基本概念、实现方式…

增材制造(3D打印):为何备受制造业瞩目?

在科技浪潮的推动下&#xff0c;增材制造——即3D打印技术&#xff0c;正逐步成为制造业领域的璀璨新星&#xff0c;吸引了航空航天、汽车、家电、电子等众多行业的目光。那么&#xff0c;是什么让3D打印技术如此引人注目并广泛应用于制造领域&#xff1f;其背后的核心优势又是…

VSCODE SSH连接失败

前提&#xff1a;以前连接得好好的 突然有一天就连接不上了 打开C盘下的known_hosts文件删除如下内容&#xff0c;重新登陆即可

天正如何保存低版本

打开天正cad的界面。左边找到文件布图这个菜单&#xff0c;点击进入找到图形导出这个子菜单&#xff0c;之后会出现下面这一界面。 第2步 可以看到保存类型&#xff0c;一进去是天正3文件的&#xff0c;这时候你要点开下拉选择天正6文件&#xff0c;其它可以不用修o改&#x…

Keepalived和Nginx一起在Centos7上实现Nginx高可用设计

方案概览 如需详细信息可点击下列链接进行视频观看 B站 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP 抖音 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP Centos7 yum更新 安装阿里yum源 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Cent…

TCP/UDP的对比,粘包分包抓包,http协议

服务器端&#xff1a; 一、loop 127.0.0.1本地回环测试地址 二、tcp特点 面向连接、可靠传输、字节流 粘包问题&#xff1a;tcp流式套接字&#xff0c;数据与数据之间没有套接字&#xff0c;导致可能多次的数据粘到一起 解决方法&#xff1a;&#xff08;1&#xff09;规…

Linux数据相关第1个服务_备份服务rsync

1、备份服务概述 备份服务&#xff1a;需要使用到脚本&#xff0c;打包备份&#xff0c;定时任务 备份服务&#xff1a;rsyncd 服务&#xff0c;不同主机之间数据传输 特点: rsync是个服务也是命令使用方便&#xff0c;具有多种模式传输数据的时候是增量传输 增量与全量&am…

Nginx: 配置项之http模块connection和request的用法以及limit_conn和limit_req模块

connection和request connection 就是一个连接, TCP连接 客户端和服务器想要进行通信的话&#xff0c;有很多种方式比如说, TCP的形式或者是UDP形式的通常很多应用都是建立在这个TCP之上的所以, 客户端和服务器通信&#xff0c;使用了TCP协议的话&#xff0c;必然涉及建立TCP连…