数据结构(八):图介绍及面试常考算法

一、图介绍

1、定义

图由结点的有穷集合V和边的集合E组成。其中,结点也称为顶点。一对结点(x, y)称为边(edge),表示顶点x连接到顶点y。边可以包含权重/成本,显示从顶点x到y所需的成本。若两个顶点之前存在一条边,就表示这两个顶点具有相邻关系。

2、类型

(1)无向图

(2)有向图

3、表示方法

(1)邻接矩阵

邻接矩阵存储结构就是用矩阵表示图中各顶点之间的邻接关系,两个顶点有邻接关系,就记录为1,否则为0。

(2)邻接表

邻接表是图的一种顺序存储与链式存储相结合的存储方式。

4、遍历方法

(1)广度优先搜索

(2)深度优先搜索

二、面试常考的算法

1、实现深度优先搜索

 题目:如下无向图,从节点A开始遍历,其深度优先搜索为:A B D E F C。

思路:深度优先搜索,创建visited数组,用于记录所有被访问过的顶点。

(1)从A出发,访问A。

(2)找出A的第一个没有被访问的邻接点,访问该点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。

(3)返回前一个访问过的仍有未被访问邻接点的顶点,继续访问该顶点的下一个未被访问邻接点。

(4)重复2,3步骤,直至所有顶点均被访问,搜索结束。

#include<iostream>
using namespace std;
#include<vector>
#include<set>
#include<unordered_set>
#include<map>class Graph{private:map<char, vector<char>> graph;  //创建图,graph记录图的节点及邻接关系public:void add_edge(char node, vector<char> neibors){graph[node] = neibors;}void dfs_helper(char node, map<char, int>& visited){visited[node] = 1;cout << node << " ";for(auto n: graph[node]){// 判断当前节点的邻接节点有无被访问if(visited[n] != 1)dfs_helper(n, visited);}}void dfs(char start_node){map<char,int> visited; // visited数组用来记录所有被访问过的顶点,被访问过,就记为1dfs_helper(start_node, visited);}  
};int main(){Graph graph;graph.add_edge('A', {'B', 'C'});graph.add_edge('B', {'A', 'D', 'E'});graph.add_edge('C', {'A', 'F'});graph.add_edge('D', {'B'});graph.add_edge('E', {'B', 'F'});graph.add_edge('F', {'C', 'E'});graph.dfs('A');
}

 

2、实现广度优先搜索

题目:如下无向图,从节点A开始遍历,其广度优先搜索为:A B D C E,从节点B开始遍历,其广度优先搜索为:B A C D E。

思路:广度优先搜索,利用队列来实现。把访问到的顶点入队,再访问该顶点的所有相邻的顶点,等访问完了该顶点的邻接点,再出队顶点和其相邻的顶点,每出队一个,就入队该顶点的未访问过的邻接顶点,重复上述步骤,直到队列为空。

#include<iostream>
#include<queue>
#include<map>
using namespace std;
class Graph{private:map<char, vector<char>> graph;  //创建图,graph记录图的节点及邻接关系public:void add_edge(char node, vector<char> neibors){graph[node] = neibors;}// 层次(广度)遍历void bfs(char node){map<char, int> visited; // visited数组用来记录所有被访问过的顶点,被访问过,就记为1queue<char> que;que.push(node);visited[node] = 1;while(!que.empty()){char temp = que.front(); //出队que.pop();cout << temp << " ";node = temp; //记录出队的点for(auto neibor: graph[node]){if(visited[neibor] != 1)que.push(neibor); //访问出队点的邻接点,并入队visited[neibor] = 1; //已访问的顶点标记为1}}}
};int main(){Graph graph;graph.add_edge('A', {'B', 'D'});graph.add_edge('B', {'A', 'C', 'D'});graph.add_edge('C', {'B', 'D', 'E'});graph.add_edge('D', {'A', 'B', 'C', 'E'});graph.add_edge('E', {'C', 'D'});graph.bfs('B');
}

 

3、利用拓扑排序判断图是否有环路。

题目如下有向图,判断是否存在环路。如果不为树,输出其拓扑排序。

思路通过BFS实现拓扑排序。

(1)首先计算每个节点的入度,将所有入度为0的节点放入队列中

(2)开始执行BFS,我们取队首的节点u,放入结果中;移除u的所有出边,即将u的所有相邻节点的入度减少1,判断如果某个相邻的节点v的入度变为0,就将v放入队列中。

(3)当BFS结束后,如果答案中包含的节点数和图中的节点数相等,那么就得到了图G的拓扑排序,否则说明图中存在环,不存在拓扑排序。

// 利用拓扑排序判断有向图是否存在回路
#include<iostream>
#include<map>
#include<vector>
#include<queue>
using namespace std;
class Graph{private:map<char, vector<char>> graph;public:void add_edge(char node, vector<char> neibors){graph[node] = neibors;}// 广度优先搜索+队列判断void has_cycle(){map<char, int> indegree; //记录每个顶点的入度,默认为0for(auto g: graph){indegree[g.first] = 0;}//计算每个顶点的入度for(auto g: graph){char n = g.first;for(auto nei: graph[n]){indegree[nei] += 1;}}// 1、将所有入度为0的顶点入队queue<char> que;for(auto in: indegree){char c = in.first;if(in.second == 0)que.push(c);}// 2、开始执行BFS,每出队一个顶点,就将该顶点的所有边移除,// 即将该顶点所有相邻节点的入度减1,如果某个相邻节点的入度变为0,就将该节点放入队列中vector<char> tuopu;while(!que.empty()){char temp = que.front();que.pop();tuopu.push_back(temp);for(auto neibor: graph[temp]){indegree[neibor] -= 1;if(indegree[neibor] == 0)que.push(neibor);}}// 3.判断拓扑排序结果里的顶点个数,如果和图的个数相等,则没有环if(tuopu.size() == graph.size()){cout << "该图没有环,为树" << endl << "拓扑排序为:";for(auto t: tuopu){cout << t << " ";}    }elsecout << "该图有环,不为树";}
};int main(){// 没有环Graph graph;graph.add_edge('A', {'B', 'C'});graph.add_edge('B',{});graph.add_edge('C',{'D','E'});graph.add_edge('D', {'B'});graph.add_edge('E', {});graph.has_cycle();// 有环Graph graph2;graph2.add_edge('A', {'B', 'C'});graph2.add_edge('B', {'C'});graph2.add_edge('C',{'D','E'});graph2.add_edge('D', {'B'});graph2.add_edge('E', {});graph2.has_cycle();return 0;
}

 

4、计算图的边数

题目:给定如下无向图,输出该图的边数7。

思路:遍历图中的节点,若该节点的邻接节点没有被访问,则边数count+1。

#include<iostream>
#include<queue>
#include<map>
using namespace std;
class Graph{private:map<char, vector<char>> graph;  //创建图,graph记录图的节点及邻接关系public:void add_edge(char node, vector<char> neibors){graph[node] = neibors;}// 计算图的边数void getNumsofEdge(){map<char, int> visited; //记录节点是否访问int count = 0;// 遍历graph中的节点与邻接节点for(auto g: graph){char n = g.first;for(auto neibor: graph[n]){if(visited[neibor] != 1){count += 1;}// 每遍历一个节点,就将该节点标记为1visited[n] = 1;}}cout << count;}
};
int main(){Graph graph;graph.add_edge('A', {'B', 'D'});graph.add_edge('B', {'A', 'C', 'D'});graph.add_edge('C', {'B', 'D', 'E'});graph.add_edge('D', {'A', 'B', 'C', 'E'});graph.add_edge('E', {'C', 'D'});graph.getNumsofEdge();
}

 

5、找到两个顶点之间的最短路径

题目:如下无向图,A到F的路径有A->B->C->E->F,A->B->C->F,A->D->E->F,输出最短路径A->D->E->F。

 思路:广度优先搜索(BFS)+ 队列实现。

(1)准备queue和map,queue用于BFS,map<char, vector<char>>用于存储当前最短距离。

(2)BFS,将顶点node1入队,并向Map中添加键值对。

(3)当队列非空时,进行循环。现将队首元素x出队,当前路径等x的当前路径,然后遍历x的邻接节点,如果邻接点中的某个节点tmp不在map键值对中(相当于未访问过), 就向Map中加入键值对<tmp,当前路径>,并将tmp入队,如果tmp为node2,就返回Map中tmp对应的路径。

#include<iostream>
#include<queue>
#include<map>
using namespace std;
class Graph{private:map<char, vector<char>> graph;  //创建图,graph记录图的节点及邻接关系public:void add_edge(char node, vector<char> neibors){graph[node] = neibors;}// 计算两个顶点的最短路径:A->B->D->Fvector<char> getShortPath(char node1, char node2){map<char, vector<char>> ShortPath;  //用于存储当前最短路径queue<char> q; //用于广度优先搜索// 如果求自身到自身的最短路径,返回node1->node2if(node1 == node2){return {node1, node2};}//否则将node1入队,并向map中加入键值对q.push(node1);ShortPath[node1] = {node1};while(!q.empty()){char temp = q.front();q.pop();vector<char> path = ShortPath[temp];for(auto neibor: graph[temp]){if(ShortPath.find(neibor) == ShortPath.end()){// 如果邻接节点不在map中(相当于未访问过),就向map中加入键值对ShortPath[neibor] = path;ShortPath[neibor].push_back(neibor);q.push(neibor);if(neibor == node2)return ShortPath[neibor];}}}return {};}
};
int main(){Graph graph;graph.add_edge('A', {'B', 'D'});graph.add_edge('B', {'A', 'C', 'D'});graph.add_edge('C', {'B', 'D', 'E'});graph.add_edge('D', {'A', 'B', 'C', 'E'});graph.add_edge('E', {'C', 'D', 'F'});graph.add_edge('F', {'C','E'});vector<char> short_path = graph.getShortPath('A', 'F');for(auto s: short_path){cout << s;}      
}

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最小二乘法在线性回归中的用法

最小二乘法在线性回归中的用法 最小二乘法是1809年高斯在《天体运动论》中公开发表的一种计算方法。最小二乘法其实是一种“最佳猜测”的方法。想象一下&#xff0c;你有一些数据点&#xff0c;比如你朋友的身高和体重。你想找到一个简单的规则来描述这两者之间的关系。最小二…

SpringIOC之ScopedProxyCreator

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

深入了解Linux信号:作用、产生、捕捉和阻塞

这里写目录标题 引言1. 信号的基本概念1.1 信号的分类和编号&#xff1a;1.2 查看信号默认处理动作1.3 信号的作用1.4 信号的产生1.4.1通过终端按键产生1.4.2通过系统函数向进程发信号1.4.3由软件条件产生信号1.4.4硬件异常产生信号 2. 常见信号及其作用SIGINT (2) - 中断信号&…

宝塔Linux:部署His医疗项目通过jar包的方式

&#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​​​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Linux》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有…

LVGL 显示图片

LVGL 显示图片 LVGL显示图片1. 显示图片文件2. 显示C数组格式3. 显示RAM中的图像文件4. 图像符号显示5. 显示GIF动画LVGL显示图片代码分析 LVGL显示图片 lvgl 8.3版本默认支持PNG,BMP,JPG,SJPG和GIF动图等格式的图片显示&#xff1b; 需要在lv_conf.h配置文件里使能对应图片的…

docker 在线安装redis

1、远程仓库拉取redis镜像&#xff0c; docker pull redis&#xff0c;默认拉取最新版本 2、在本地宿主机文件夹下创建相关目录文件&#xff0c;供容器卷使用&#xff0c;创建 /usr/local/data/redisdocker/data 文件夹&#xff0c;准备一个纯净版 redis.conf 配置文件 &#x…

12V升18V4A同步升压恒压WT3210

12V升18V4A同步升压恒压WT3210 WT3210 是一款高功率密度的全集成同步升压转换器&#xff0c;内部集成的功率MOSFET管导通电阻为上管8mΩ和下管15mΩ。可为便携式系统提供空间小尺寸 解决方案。WT3210具有 2.7V 至 20V 的宽输入电压范围&#xff0c;应用在单节或两节锂电池的便携…

网宿配置cdn加速

1、商务谈妥后我们登录新注册的账号可以找到我们的服务 我们的服务叫“CDN Pro” 2、添加ssl证书 1&#xff09;点击证书 2&#xff09;新建证书 会有默认提示&#xff0c;名字自定义 将我们购买的证书上传后保存 3&#xff09;结果如下 4&#xff09;查询我们的证书详情 …

OpenHarmony鸿蒙原生应用开发,ArkTS、ArkUI学习踩坑学习笔记,持续更新中。

一、AMD处理器win10系统下&#xff0c;DevEco Studio模拟器启动失败解决办法。 结论&#xff1a;在BIOS里面将Hyper-V打开&#xff0c;DevEco Studio模拟器可以成功启动。 二、ArkTS自定义组件导出、引用实现。 如果在另外的文件中引用组件&#xff0c;需要使用export关键字导…

数字化技术助力英语习得 iEnglish成智慧化学习新选择

日前,美剧《老友记》中钱德勒的扮演者马修派瑞去世的消息引发不少人的回忆杀。《老友记》官方发文悼念马修派瑞:“对于马修派瑞去世的消息,我们深感悲痛,他是给我们所有人的真正礼物,我们的心和他的家人、爱人、所有的粉丝在一起。” 作为不少国人刷剧学习英语的首选,《老友记…

【Linux】键盘高级操作技巧

命令行最为珍视的目标之一就是懒惰&#xff1a;用最少的击键次数来完成最多的工作。另一个目标是你的手指永 远不必离开键盘&#xff0c;永不触摸鼠标。因此&#xff0c;我们有必要了解一些键盘操作&#xff0c;使我们用起来更加的迅速和高效。 移动光标 在前面介绍过上下左右…

二叉搜索树 和 哈希表 (JAVA)

目录 二叉搜索树 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 哈希表 哈希冲突 闭散列 线性探测法 二次探测法 开散列 开散列代码实现&#xff1a; 插入元素 删除元素 查找元素 二叉搜索树 先了解一下二叉搜索树是啥&#xff0c;概念如下&#xff1a…