【算法与图】通向高效解决方案的钥匙

【算法与图】通向高效解决方案的钥匙
在这里插入图片描述

文章目录

遍历算法

BFS(广度优先遍历)

1. 什么是 BFS?

BFS(广度优先搜索)是一种图的遍历算法,用于从一个起始节点出发,逐层访问图中的所有节点。其基本流程如下:

  1. 起始节点:选择一个节点作为起点。
  2. 队列:使用队列(FIFO)来保存待访问的节点。
  3. 访问过程
    • 将起始节点加入队列并标记为已访问。
    • 当队列不为空时:
      • 从队列中取出一个节点,访问该节点。
      • 将该节点的所有未访问邻居节点加入队列并标记为已访问。
  4. 层级遍历:BFS 会先访问距离起始节点最近的节点,然后逐层向外扩展,直到所有可以访问的节点都被访问。

2. 特点和应用

  • 最短路径:在无权图中,BFS 可以找到从起始节点到其他节点的最短路径。
  • 图的连通性:可以用来判断图的连通性,即判断两个节点是否在同一连通分量中。
  • 应用:广泛应用于网络流、社交网络分析、最短路径问题、迷宫求解等领域。

3. BFS 示例

假设我们有一个无向图,节点间的连接如下:

在这里插入图片描述


应该如何实现这种算法呢?
首先我们应该用一个队列来维护节点,进入BFS这个接口的时候,我们应该传入从哪个节点开始进行BFS。
然后用一个队列来维护节点,先将第一个节点push进队列中,然后将这个节点输出之后,先将这个节点保存起来然后再把这个节点pop掉,然后将与这个节点相连的节点push进队列(注意:这里可能会出现重复的节点,比如我们拿上面的例子为例,第一次push的时候我们将C节点已经push进队列了,但是第二层访问完了之后,到第三层的时候,B和C相连还会遍历一次C,所以这里我们应该用一个vector进行标记,标记这个节点被访问过没有),进入循环的条件是队列是否为空。
代码展示:

voidBFS(const V& src){size_t srci =GetVertexIndex(src); queue<int> q; q.push(srci);//队列 vector<bool>visited(_vertexs.size(), false);//标记数组 visited[srci]= true;int levelsize =1;size_t n = _vertexs.size();while(!q.empty()){for(int i =0;i < levelsize;i++){int front = q.front(); cout << front <<':'<< _vertexs[front]<<' '; q.pop();//把front顶点的邻接顶点入队列for(size_t i =0;i < _vertexs.size();i++){if(_matrix[front][i]!= MAX_W && visited[i]== false){ q.push(i); visited[i]= true;}}} cout << endl;//levelzie是下一层的数据个数 levelsize = q.size();}}

DFS(深度优先搜索)

1. 什么是 DFS?

DFS(深度优先搜索)是一种图的遍历算法,从起始节点开始,尽可能深入探索每个分支,直到无法再继续,然后回溯到上一个节点,继续探索其他分支。它适用于有向图和无向图。

2. DFS 的基本步骤

  1. 起始节点:选择一个节点作为起点。
  2. 深入探索:访问起始节点,并标记为已访问。
  3. 递归访问:对当前节点的每个未访问邻居节点递归进行深度优先搜索。
  4. 回溯:如果当前节点的所有邻居都被访问,则回溯到上一个节点,继续深度搜索其他分支。

3. 特点

  • 优先深入:DFS 尽可能先访问一个节点的最深分支,再逐步回溯到较浅的分支。
  • 非最短路径:与 BFS 不同,DFS 不保证找到最短路径,因为它按深度优先进行搜索。
  • 应用广泛:DFS 可用于检测图的连通性、拓扑排序、寻找路径和检测环。

4. DFS 的应用

  • 路径搜索:可以用于寻找图中从一个节点到另一个节点的路径。
  • 图的连通性:判断图中的连通分量。
  • 拓扑排序:用于有向无环图(DAG)的节点排序。
  • 检测环:可以检测图中是否存在环。

5. DFS 示例

在这里插入图片描述


DFS和BFS一样,还是给定一个起点,从这个起点开始进行,但是DFS的方式和BFS完全不同,DFS是一条路走到黑,从当前节点一直走,走到不能走为止,当走到不能走时,进行回溯,回溯到上一个岔口,然后向刚刚没有走过的路口继续走,走到尽头的时候又进行回溯,就一直这样递归,直到把所有节点遍历完为止。

代码展示:

void_DFS(size_t srci, vector<bool>& visited){//进来直接访问这个点 cout << srci <<':'<< _vertexs[srci]<< endl; visited[srci]= true;//找到下一个srci相邻的没有访问过的点,去往深度遍历for(size_t i =0;i < _vertexs.size();i++){//如果下一个节点没有被访问过直接遍历if(_matrix[srci][i]!= MAX_W && visited[i]== false){_DFS(i, visited);}}}voidDFS(const V& src){//获取起点下标size_t srci =GetVertexIndex(src); vector<bool>visited(_vertexs.size(), false);_DFS(srci, visited);}

注意:DFS中也需要用一个bool数组进行标记,当前位置是否被访问过。

最小生成树问题

1. 什么是最小生成树?

最小生成树是一个图的子集,包含图中的所有节点,并且是连通的,同时边的总权重最小。最小生成树的特点是没有回路,并且连接了图中的所有节点。

2. 最小生成树的基本特性

  • 包含所有节点:最小生成树包含图中的所有顶点。
  • 边的权重总和最小:在所有可能的生成树中,其边权重之和是最小的。
  • 无环图:最小生成树是一个无环的连通图。

3. 应用场景

  • 网络设计:如计算机网络、交通网络的最优连接。
  • 电路设计:用于布线问题,减少电缆长度。
  • 聚类分析:在数据科学中,用于分类和分组。

4. 最小生成树的算法

常用的求解最小生成树的算法有:

  1. Kruskal 算法
    • 通过选择边的方式逐步构建最小生成树,优先选择权重最小的边,确保不形成回路。
  2. Prim 算法
    • 从一个起始节点开始,逐步扩展生成树,选择连接已包含节点和未包含节点的最小权重边。

5. 最小生成树的图示:

在这里插入图片描述

下面的图就是上面的图的最小生成树的其中之一。最小生成树是不止一个的。如果我们选择上面那个8,最小生成树又不一样。

在这里插入图片描述

最小生成树算法

Kruskal算法

1. 什么是Kruskal算法?

克鲁斯卡尔算法是一种用于求解最小生成树(MST)的贪心算法。它通过选择边的方式逐步构建最小生成树,优先选择权重最小的边,并确保不形成回路。

2. 算法步骤

克鲁斯卡尔算法的基本步骤如下:

  1. 排序边:将图中的所有边按权重从小到大排序。
  2. 初始化:创建一个空的生成树,并初始化一个并查集(Union-Find)结构,用于检测图中的环。
  3. 选择边
    • 从权重最小的边开始,依次考虑每条边。
    • 对于每条边 (u, v),检查 uv 是否在同一连通分量中(使用并查集)。
    • 如果不在同一连通分量中,加入该边到生成树,并将 uv 的连通分量合并。
  4. 结束条件:当生成树中的边数等于 V-1V 为节点数)时,算法结束。

3. 算法复杂度

  • 时间复杂度O(E log E),其中 E 是边的数量,主要由排序边的时间决定。
  • 空间复杂度O(V),用于存储并查集。

4. 示例

在这里插入图片描述

5.思路及代码

根据克鲁斯卡尔算法的步骤:

  1. 排序边,我们可以用优先级队列来对边进行排序,但是这个边不能只有边,应该还需要边两端的顶点,所以我们需要把这个封装成一个结构体,也就是边集,还有一个问题就是排序,优先级队列的排序默认是从大到小,所以我们还需要写一个比较的逻辑进行比较。
  2. 初始化:初始化我们只需要将最小生成树初始化为原图的大小即可。大小和原图一模一样,顶点映射下标的关系也和原图一模一样。
  3. 选择边:在选择边时,我们只需要将存在优先级队列中的边取出来即可,但是在选择边时需要检查一下这个边加入之后是否会形成环,这时就可以利用并查集,因为并查集可以高效管理集合,我们开辟一个并查集的大小是顶点个数的并查集,如果这条边选了就将这两个顶点的集合合并,如果会形成环的话,那么对应的两个顶点肯定在一个集合当中。所以我们只需要判断这两个顶点是否在一个集合当中即可,如果没在一个集合当中,就将这条边给最小生成树,并且将这两个顶点的集合合并,还需要一个累加权值的变量记录总的权值大小。
  4. 结束条件:用size记录边数的大小,当边的条数等于顶点的个数-1的时候就是结束的时候。

代码展示:

W Kruskal(Self& minTree){size_t n = _vertexs.size(); minTree._vertexs = _vertexs; minTree._indexMap = _indexMap; minTree._matrix.resize(n);for(auto& e : minTree._matrix) e.resize(n, MAX_W);//优先级队列,优先级默认是大的优先级高,所以这里要控制 priority_queue<Edge, vector<Edge>, greater<Edge>> minq;for(int i =0;i < n;i++){for(int j =0;j < n;j++){//i<j保证只访问一次if(i < j && _matrix[i][j]!= MAX_W);{//将edge插入进去,由于无向图会出现重复添加的情况,所以无向图走一半即可 minq.push(Edge(i, j, _matrix[i][j]));}}}//选出n-1条边//开辟一个n个顶点的并查集size_t size =0;//总的权值 W totalW =W(); UnionFindset ufs(n);while(!minq.empty()){//选择一条边 Edge eg = minq.top(); minq.pop();//观察两个顶点是否在同一个集合if(!ufs.IsInSet(eg._srci, eg._dsti)){ cout << _vertexs[eg._dsti]<<"->"<< _vertexs[eg._srci]<<':'<< eg._w << endl;//将边添加到mintree中 minTree._AddEdge(eg._srci, eg._dsti, eg._w);//将顶点添加到并查集当中 ufs.Union(eg._srci, eg._dsti); totalW += eg._w;++size;}}if(size == n -1)return totalW;elsereturnW();}

Prim算法

1. 什么是Prim算法?

普利姆算法是一种用于求解最小生成树(MST)的贪心算法。它从一个节点开始,通过逐步选择连接已访问节点和未访问节点的最小权重边来扩展生成树,直到所有节点都被包含。

2. 算法步骤

普利姆算法的基本步骤如下:

  1. 选择起始节点:从图中的任意一个节点开始(通常是第一个节点)。
  2. 初始化:将起始节点加入生成树,并将它的所有邻边放入一个优先队列(最小堆),按边的权重排序。
  3. 选择最小权重边:从优先队列中取出权重最小的边,并检查其连接的节点是否已在生成树中。
    • 如果该节点已在生成树中,忽略这条边。
    • 如果该节点不在生成树中,将该节点和边加入生成树,并将新节点的所有邻边加入优先队列。
  4. 重复:不断从优先队列中选取最小权重边,直到生成树包含所有节点。

3. 算法复杂度

  • 时间复杂度O(E log V),其中 E 是边的数量,V 是节点数量,主要由最小堆操作决定。
  • 空间复杂度O(V^2)

4. 示例

在这里插入图片描述

5.思想及代码

普林姆算法和克里姆林算法还是有很大的差别的,普林姆算法需要起始点,然后将起始点相连最小的边入到边集当中。

在这里插入图片描述


假设这个图我们以a点为例,和a相连的边是b和h点,ab边明显小于ah边,所以我们选择ab边

在这里插入图片描述


这里引入了b点,所以我们的选择是bc边,bh边还有ah边,很明显这里我们选择bc边也可以,ah边也可以,所以我们就选择ah边吧。

在这里插入图片描述


这里我们可以选择的边种最小的是hg边。

在这里插入图片描述


接下来我们肯定选择gf,因为gf最小

在这里插入图片描述


最后可以推出最小生成树

在这里插入图片描述


Prim算法的思路很简单,实现过程我们还是用优先级队列,但是在选择的时候我们需要判断一下是否形成环。
代码展示:

W Prim(Self& minTree,const V& src){//初始化size_t srci =GetVertexIndex(src);size_t n = _vertexs.size(); minTree._vertexs = _vertexs; minTree._indexMap = _indexMap; minTree._matrix.resize(n);for(auto& e : minTree._matrix)e.resize(n, MAX_W);//选过的顶点 vector<bool>X(n, false); vector<bool>Y(n, true); X[srci]= true, Y[srci]= false;//从X->Y集合中连接的边里面选出最小的边 priority_queue<Edge, vector<Edge>, greater<Edge>> minq;for(size_t i =0;i < _vertexs.size();i++){//把srci连接的边添加到队列中if(_matrix[srci][i]!= MAX_W)minq.push(Edge(srci, i, _matrix[srci][i]));} cout <<"Prim开始选边"<< endl;size_t size =0; W totalW =W();while(!minq.empty()){ Edge min = minq.top(); minq.pop();//如果最小边的目标点也在起点集合if(X[min._dsti]){ cout <<"构成环:"; cout << _vertexs[min._srci]<<"->"<< _vertexs[min._dsti]<<":"<< min._w << endl;}else{ minTree._AddEdge(min._srci, min._dsti, min._w); cout << _vertexs[min._srci]<<"->"<< _vertexs[min._dsti]<<":"<< min._w << endl; size++; totalW += min._w;if(size == n -1)break;//将dsti添加到X集合 X[min._dsti]= true; Y[min._dsti]= false;for(size_t i =0;i < _vertexs.size();i++){//将目标点连接的边添加到集合当中并且需要判断目标点是否已经在集合当中if(_matrix[min._dsti][i]!= MAX_W && X[i]== false)minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));}}}if(size == n -1)return totalW;elsereturnW();}

结尾总结

通过本文的讲解,我们深入了解了图论中的三种经典算法:广度优先搜索(BFS)、深度优先搜索(DFS)和最小生成树(Kruskal算法和Prim算法)。这些算法在计算机科学、数据分析、人工智能等领域有着广泛的应用。

从简单的图遍历到复杂的网络优化问题,这些算法都展现了其强大的解决问题能力。然而,图论是一个庞大的领域,还有许多更深入、更复杂的算法等待我们去探索。例如,拓扑排序、强连通分量、最短路径问题等。

希望本文能为你打开图论算法的大门,激发你对算法学习的兴趣。在未来的学习中,我们可以继续深入研究图论算法,并将其应用到实际的项目中。

Read more

解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是Docker 2、什么是Ollama 二、准备工作 1、操作系统 2、镜像准备 三、安装 1、安装Docker 2、启动Ollama 3、拉取Deepseek大模型 4、启动Deepseek  一、引言 1、什么是Docker Docker:就像一个“打包好的App” 想象一下,你写了一个很棒的程序,在自己的电脑上运行得很好。但当你把它发给别人,可能会遇到各种问题: * “这个软件需要 Python 3.8,但我只有 Python 3.6!

By Ne0inhk
深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk
【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

系列篇章💥 No.文章01【DeepSeek应用实践】DeepSeek接入Word、WPS方法详解:无需代码,轻松实现智能办公助手功能02【DeepSeek应用实践】通义灵码 + DeepSeek:AI 编程助手的实战指南03【DeepSeek应用实践】Cline集成DeepSeek:开源AI编程助手,终端与Web开发的超强助力04【DeepSeek开发入门】DeepSeek API 开发初体验05【DeepSeek开发入门】DeepSeek API高级开发指南(推理与多轮对话机器人实践)06【DeepSeek开发入门】Function Calling 函数功能应用实战指南07【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:本地部署与API服务快速上手08【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:Web聊天机器人部署指南09【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:基于vLLM 搭建高性能推理服务器10【DeepSeek部署实战】基于Ollama快速部署Dee

By Ne0inhk