c++数据结构与算法 图
图的定义:
图:用线或者边连接在一起的节点集合。G=(V,E)
当且仅当(i,j)是图的边,则称节点i,j是邻接的。
有向图和无向图;
加权图;
一条路径,如果除第一个和最后一个节点之外,其他节点各不相同,则这条路径称为简单路径;
生成树:设G是一个无向图,G是联通的,当且仅当G的每一对顶点之间都有一条路径。假设G是联通的,但G的有些边对联通来说是不必要的,去掉这些边依然联通
子图:如果图H的顶点和边的集合分别是G的顶点和边的集合的子集,那么图H是G的子图。
环路:起点和终点相同的简单路径称为环路:
没有环路的联通无向图是一棵树,一个G的子图,如果包含G的所有顶点,且是一棵树,则成为G的生成树。
图的特性:
1.在无向图中,与节点i相关联的边的数目称为节点i的度,假设在图G中,节点的数量为n,边的数量为e,则有:
即所有顶点的度数之和为边的数目的两倍,因为每一条边都与两个节点相连。
在具有n个节点的图中,边的最小数目是0, 最大的数目是,每个节点与其他n-1个节点连接,则n个节点共有n*(n-1)条边,再出去重复的:则最后为n*(n-1)/2
同理:
在有向图G中,节点的度分为入度和出度:
图的ADT:
无权图的描述:
邻接矩阵,邻接链表,邻接数组
1.邻接矩阵:
n个节点的图: n*n的邻接矩阵,矩阵元素值为0,1(代表连接,或者不连接)、
无向图的邻接矩阵是对称的,所以只需存储上三角或者下三角。
2. 邻接链表
节点i的邻接表是一个线性表,它包含所有邻接于节点i的节点。
在邻接链表米描述中,图的每个节点都有一个邻接表,可以使用一个保存链表的数组保存所有的顶点。
3. 邻接数组
在邻接数组中,每一个节点的线性表使用一个数组线性表表示而不是链表。
加权图的描述:
对无权图的描述稍作扩充即可表示加权图的描述
图的ADT实现:(邻接矩阵)
基类实现: graph.h文件
// 图的ABC实现 #ifndef GRAPH_H #define GRAPH_H template<typename T> class graph { public: virtual ~graph() {}; // ADT virtual int numberOfVertices() const=0; // 返回节点数量 virtual int numberOfEdges() const=0; // 返回边的数量 virtual bool existEdge(int i, int j) const=0; // 返回某条边(i,j)是否存在 virtual void insertEdge(edge<T>* e) =0; // 插入一条边 virtual void eraseEdge(int i, int j) =0; // 删除一条边 virtual int degree(int i) const=0; // 节点i的度 无向图 virtual int outDegree(int i) const=0; // 出度 virtual int inDegree(int i) const=0; // 入度 // others; virtual bool directed() const=0; virtual bool weighted() const=0; }; #endif
一共有四种图,无权无向图,加权无向图,无权有向图,加权有向图
其中每种图都可以有三种实现方法:邻接矩阵,邻接链表,邻接数组
1. 邻接矩阵类的实现:
因为需要有在图中插入边的操作,所以定义类edge,表示需要插入的边:
edge.h文件:
#ifndef EDGE_H #define EDGE_H template<typename T> class edge // 一条边 { private: int vertex1; int vertex2; T weight; public: /* edge() { vertex1 = 0; // 节点1 vertex2 = 0; // 节点2 weight = 0; // 权重 } */ edge(int v1, int v2) // 无权边 { vertex1 = v1; vertex2 = v2; weight = 1; } edge(int v1, int v2, T w) { vertex1 = v1; vertex2 = v2; weight = w; } int getvetex1() { return vertex1; } int getvetex2() { return vertex2; } T getweight() { return weight; } }; #endif
在图中插入边和删除边的时候,需要首先检测的边的合法性:如果不合法,程序需要抛出异,这个由自定义的异常类实现:
invalidVertextExcept.h文件:
#ifndef INVALID_VERTEX_H #define INVALID_VERTEX_H #include <iostream> #include <stdexcept> using namespace std; class invalidVertex : public runtime_error { public: invalidVertex() : runtime_error("The vertex must between 1 and n") { } }; #endif
同时,再调用还未定义的方法的时候,程序也需要抛出异常,这个由自定义异常类undefinedMethedEXcept实现:
undefinedMethedExcept.h文件:
#ifndef INVALID_VERTEX_H #define INVALID_VERTEX_H #include <iostream> #include <stdexcept> using namespace std; class invalidVertex : public runtime_error { public: invalidVertex() : runtime_error("The vertex must between 1 and n") { } }; #endif
图的数据结构实现:
adjacencyWeightedG.h文件:
#ifndef ADJACENCYWEIGHTEDG_H #define ADJACENCYWEIGHTEDG_H #include <iostream> #include "E:\back_up\code\c_plus_code\graph_project\external_file\graph.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\edge.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\invalidVertextExcept.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\undefinedMethodExcept.h" using namespace std; template<typename T> class adjacencyWgraph : public graph<T> { private: int n; // 节点个数 int e; // 边的数目 T** matrix; // 邻接矩阵 T noEdge; // 表示不存在的边的值 public: adjacencyWgraph(int vertex_num, T theNoEdge); // 构造函数 adjacencyWgraph(adjacencyWgraph<T>& gar); // 拷贝构造函数 ~adjacencyWgraph(); // 析构函数 int numberOfVertices() const; int numberOfEdges() const; bool directed() const; bool weighted() const; bool existEdge(int i, int j) const; void insertEdge(edge<T>& theEdge); void eraseEdge(int i, int j); int degree(int i) const; int outDegree(int i) const; int inDegree(int i) const; void outputMatrix() const; void addNode(); // 在图种增加一个节点 }; template<typename T> adjacencyWgraph<T>::adjacencyWgraph(int vertex_num, T theNoEdge) { // 节点数 if(vertex_num<0) { //cout << "Number of vertex must >= 0" << endl; //exit(0); throw invalidVertex(); } n = vertex_num; // 节点数 e = 0; noEdge = theNoEdge; // 创建邻接矩阵 matrix = new T*[n+1]; for(int i=0; i<n+1; i++) { matrix[i] = new T[n+1]; } // 初始化邻接矩阵 for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { matrix[i][j] = noEdge; // 初始化为不存在的边值 } } } template<typename T> adjacencyWgraph<T>::adjacencyWgraph(adjacencyWgraph<T>& gra) { n = gra.n; e = gra.e; noEdge = gra.noEdge; matrix = new T*[n+1]; for(int i=1; i<=n; i++) { matrix[i] = new T[n+1]; } for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { matrix[i][j] = gra.matrix[i][j]; } } } template<typename T> adjacencyWgraph<T>::~adjacencyWgraph() { for(int i=0; i<n+1; i++) { delete [] matrix[i]; } delete [] matrix; } template<typename T> int adjacencyWgraph<T>::numberOfVertices() const { return n; } template<typename T> int adjacencyWgraph<T>::numberOfEdges() const { return e; } template<typename T> bool adjacencyWgraph<T>::directed() const { return true; } template<typename T> bool adjacencyWgraph<T>::weighted() const { return true; } template<typename T> bool adjacencyWgraph<T>::existEdge(int i, int j) const { // 返回是否存在一条边 if(i<1 || j<1 || i>n || j>n || matrix[i][j]==noEdge) { return false; } else { return true; } } template<typename T> void adjacencyWgraph<T>::insertEdge(edge<T>& theEdge) // 插入一条边 { int v1 = theEdge.getvetex1(); int v2 = theEdge.getvetex2(); // 判断边的合法性 if(v1<1 || v2<1 || v1>n || v2>n) { //cout << "The edge is invalid" << endl; //return; throw invalidVertex(); } if(matrix[v1][v2]==noEdge) { e++; // 如果是插入新的边 } matrix[v1][v2] = theEdge.getweight(); // 插入一条边 } template<typename T> void adjacencyWgraph<T>::eraseEdge(int i, int j) { if(i<1 || j<1 || i>n || j>n) { //cout << "The edge is invalid" << endl; //return; throw invalidVertex(); } else if(matrix[i][j]==noEdge) { cout << "The edge not exist" << endl; return; } else { matrix[i][j] = noEdge; e--; // 边的条数 } } template<typename T> int adjacencyWgraph<T>::degree(int i) const { //cout << "undefined method" << endl; //exit(0); throw undefinedMethod(); } template<typename T> int adjacencyWgraph<T>::outDegree(int i) const { // 先判断节点的合法性 if(i<1 || i>n) { throw invalidVertex(); } int count = 0; for(int j=1; j<=n; j++) { if(matrix[i][j]!=noEdge) { count++; } } return count; } template<typename T> int adjacencyWgraph<T>::inDegree(int i) const { // 检查节点合法性; if(i<1 || i>n) { throw invalidVertex(); } int count = 0; for(int j=1; j<=n; j++) { if(matrix[j][i]!=noEdge) { count++; } } return count; } template<typename T> void adjacencyWgraph<T>::outputMatrix() const { for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { cout << matrix[i][j] << " "; } cout << endl; } cout << endl; } template<typename T> void adjacencyWgraph<T>::addNode() { T** old; old = matrix; n = n+1; //节点的数量+1 matrix = new T*[n+1]; for(int i=1; i<=n; i++) { matrix[i] = new T[n+1]; } for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { matrix[i][j] = noEdge; // 初始化邻接矩阵 } } // 将原来的数据复制过来 for(int i=1; i<=n-1; i++) { for(int j=1; j<=n-1; j++) { matrix[i][j] = old[i][j]; } } // 释放内存 for(int i=1; i<=n-1; i++) { delete [] old[i]; } delete [] old; } #endif
在类种添加了一个新的方法,addNode(),用于在图中增加新的节点。
测试代码:
mian.cpp
#include <iostream> #include "E:\back_up\code\c_plus_code\graph_project\external_file\adjacencyWeightedG.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\edge.h" using namespace std; int main(int argc, char *argv[]) { cout<<"Hello C-Free!"<<endl; adjacencyWgraph<int> graphy(5, 0); graphy.outputMatrix(); // 声明一些边 edge<int> edge1(3, 4, 9); edge<int> edge2(4, 3, 11); edge<int> edge3(2, 1, 3); edge<int> edge4(4, 6, 11); // 不满足条件的 edge<int> edge5(3, 4, 9); cout << "insert edges in graph-->" << endl; // 插入边 graphy.insertEdge(edge1); graphy.insertEdge(edge2); graphy.insertEdge(edge3); try { graphy.insertEdge(edge4); // 异常测试 } catch(invalidVertex& ex) { cout << ex.what() << endl; } graphy.outputMatrix(); graphy.insertEdge(edge5); // 插入重复的边 cout << "The edge number is " << graphy.numberOfEdges() << endl; cout << "The edge (3, 4) exist: " << graphy.existEdge(3, 4) << endl; // 删除一条边 try { graphy.eraseEdge(4, 3); } catch(invalidVertex& ex) { cout << ex.what() << endl; } graphy.outputMatrix(); cout << "The edge number is " << graphy.numberOfEdges() << endl; // 测试没有定义的方法 try { graphy.degree(4); } catch(undefinedMethod& ex) { cout << ex.what() << endl; } // 测试入度和出度 cout << "The out degree is " << graphy.outDegree(1) << endl; // 测试 addNode() cout << "Add node in graphy: " << endl; graphy.addNode(); graphy.outputMatrix(); // 插入线的节点 cout << "Insert edge in new graph: " << endl; edge<int> edge6(6, 5, 3); try { graphy.insertEdge(edge6); } catch(invalidVertex& ex) { cout << ex.what() << endl; } graphy.outputMatrix(); return 0; }
运行结果:
通过从上面的类继承和重载其中的一些方法,可以实现无向图的数据结构:
图的遍历:
例如需要从一个顶点开始,搜索所有可以到达的顶点,所谓顶点u是从顶点v可到达的,是指有一条从v到u的路径,这种路径搜索方法常有两种:广度优先搜索(breadth first search, BFS) 和深度优先搜索(depth first search, DFS), DFS效率更高,使用的更多。
1. 广度优先搜索。 BFS
假设要从顶点1开始搜索所有的可到达的顶点,可以采用:1.先确定邻接于顶点1的所有顶点集合{2,3,4}.在确定邻接于{2,3,4}的所有顶点集合{5, 6,7},再确定邻接于{5,6,7}的顶点结合{8,9}
所以从顶点1 可到达的顶点集合为{1,2,3,4,5,6,7,8,9}
这种方法称为BFS,可以使用队列实现。BFS和二叉树层次遍历相似:
例如再上面的利用邻接矩阵实现的图中定义新的方法BFS:
template<typename T> vector<int> adjacencyWgraph<T>::bfs(int node) { if(node<1 || node>n) // 节点不满足图的限制 { throw invalidVertex(); } queue<int> q; vector<int> result; // 保存满足条件的节点 int label[n+1]; for(int i=1; i<=n; i++) { label[i] = 0; // 作为标记, 未到达的顶点都标记为0 } //label[node] = 1; // 节点node已经到达: q.push(node); // 将node压入队列 while(!q.empty()) { int node_tmp = q.front(); // 弹出度列首端的元素 label[node_tmp] = 1; // node_tmp已经到达 q.pop(); // 寻找从node_tmp可到达的节点 for(int i=1; i<=n; i++) { if(i==node_tmp) { continue; } else { if(matrix[node_tmp][i] != noEdge && label[i]==0) // 从node_tmp可以到达的顶点 { // 从node_tmp到节点 i 有边且 节点 i 没有到达过 q.push(i); // 将可到达的顶点压入队列 } } } } for(int i=1; i<=n; i++) { if(label[i]==1) { result.push_back(i); // 记录可到达的顶点 } } return result; }
测试图:
假设有下面的图:
从节点1可到达的顶点为:{1,2,,4,5,6}
测试代码:
#include <iostream> #include <vector> #include "E:\back_up\code\c_plus_code\graph_project\external_file\adjacencyWeightedG.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\edge.h" using namespace std; int main(int argc, char *argv[]) { cout<<"Hello C-Free!"<<endl; adjacencyWgraph<int> graphy(6, 0); graphy.outputMatrix(); // 声明一些边 edge<int> edge1(1, 2, 9); edge<int> edge2(1, 5, 11); edge<int> edge3(3, 1, 3); edge<int> edge4(3, 5, 11); // 不满足条件的 edge<int> edge5(3, 4, 9); edge<int> edge6(5, 6, 9); edge<int> edge7(6, 4, 9); cout << "insert edges in graph-->" << endl; // 插入边 graphy.insertEdge(edge1); graphy.insertEdge(edge2); graphy.insertEdge(edge3); graphy.insertEdge(edge4); graphy.insertEdge(edge5); graphy.insertEdge(edge6); graphy.insertEdge(edge7); graphy.outputMatrix(); vector<int> res = graphy.bfs(3); for(int i=0; i<res.size(); i++) { cout << res[i] << " "; } cout << endl; return 0; }
运行结果:
深度优先搜索
深度优先搜索是另一种搜索方法,在迷宫问题中已经使用过这种方法: 迷宫问题博客
这里没有采用递归的方法实现,而是采用堆栈的方法:思路和迷宫问题相近:
template<typename T> vector<int> adjacencyWgraph<T>::dfs(int node) { if(node<1 || node>n) // 节点不满足图的限制 { throw invalidVertex(); } stack<int> node_stack; vector<int> result; int label[n+1]; for(int i=1; i<=n; i++) { label[i] = 0; } node_stack.push(node); // 将node压入栈 label[node] = 1; // node标记 while(!node_stack.empty()) { int tmp_node = node_stack.top(); // node_stack.pop(); //label[tmp_node] = 1; // 标记节点 // 寻找tmp_node的邻接节点 bool find_flag = false; // 标志位 for(int i=1; i<=n; i++) { if(tmp_node==i) { continue; } else { if(matrix[tmp_node][i]!=noEdge && label[i]==0) { //找到节点i; find_flag = true; node_stack.push(i); // 将找到的节点压入堆栈 label[i] = 1; // 标记此节点已经到达 break; } } } if(!find_flag) { node_stack.pop(); } } for(int i=1; i<=n; i++) { if(label[i]==1) { result.push_back(i); } } return result; }
测试:同样使用上面六个节点的图进行测试:
测试代码:
#include <iostream> #include <vector> #include "E:\back_up\code\c_plus_code\graph_project\external_file\adjacencyWeightedG.h" #include "E:\back_up\code\c_plus_code\graph_project\external_file\edge.h" using namespace std; int main(int argc, char *argv[]) { cout<<"Hello C-Free!"<<endl; adjacencyWgraph<int> graphy(6, 0); graphy.outputMatrix(); // 声明一些边 edge<int> edge1(1, 2, 9); edge<int> edge2(1, 5, 11); edge<int> edge3(3, 1, 3); edge<int> edge4(3, 5, 11); // 不满足条件的 edge<int> edge5(3, 4, 9); edge<int> edge6(5, 6, 9); edge<int> edge7(6, 4, 9); cout << "insert edges in graph-->" << endl; // 插入边 graphy.insertEdge(edge1); graphy.insertEdge(edge2); graphy.insertEdge(edge3); graphy.insertEdge(edge4); graphy.insertEdge(edge5); graphy.insertEdge(edge6); graphy.insertEdge(edge7); graphy.outputMatrix(); vector<int> res = graphy.dfs(1); for(int i=0; i<res.size(); i++) { cout << res[i] << " "; } cout << endl; return 0; }
测试结果:
应用:
1.在途中寻找路径:
a. 最长路径
b. 最短路径
2.检测一个图是否是连通图:
对每一个节点。执行BFS或者DFS,判断此节点是否能到达其他所有的节点,即可判断图是否为连接图
3. 生成树
在一个具有n个顶点的无向连通图中,从任何一个顶点开始进行BFS,所有的顶点都将被加上标记,可以到达n-1个当点,正好构成n-1条边;这些路径构成了一个联通子图,称为图的生成树。
广度优先生成树:按照BFS得到的生成树
深度优先生成树:按照DFS得到的生成树
---------------------------------------------------------end-----------------------------------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)