1. 图的基本概念

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:顶点集合

V = {x|x属于某个数据对象集}是有穷非空集合;
E = {(x,y)|x,y属于V}或者E = {<x, y>|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。

(x, y)表示x到y的一条双向通路,即(x, y)是无方向的;
Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的。

顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。

有向图和无向图:在有向图中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x,y>和<y, x>是两条不同的边,比如下图G3和G4为有向图。在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边,比如下图G1和G2为无向图。注意:无向边(x, y)等于有向边<x, y>和<y, x>。


完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图,比如上图G1;
在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向
相反的边,则称此图为有向完全图,比如上图G4。

邻接顶点:在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边<u, v>与顶点u和顶点v相关联。

顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。
注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径。
路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和。

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。若路 径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。


子图:设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图。


连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果图中任意一 对顶点都是连通的,则称此图为连通图。

强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到 vi的路径,则称此图是强连通图。

生成树:一个连通图的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n- 1条边。

2. 图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

  1. 邻接矩阵
    因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。

    注意:
  2. 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一定是对称的,第i行(列)元素之后就是顶点i 的出(入)度。
  3. 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替。

    用邻接矩阵存储图的有点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求。

代码:

#pragma once
#include <vector>
#include <iostream>
using namespace std;
#include <assert.h>// V---> 顶点的类型
// W---> 边所对应权值的类型
// IsDirect: false-->无向图  true--->有向图
template<class V, class W, bool IsDirect = false>
class Graph
{public:Graph(const V* pv, size_t N, const W& invalid): _vertex(pv, pv+N), _invalidWeight(invalid){// 1. 将顶点添加到图中// 2. 将保存边的矩阵初始// 二维数组行数_edges.resize(N);for (size_t i = 0; i < N; ++i)_edges[i].resize(N, invalid);}size_t GetIndexOfVertex(const V& v){for (size_t index = 0; index < _vertex.size(); ++index){if (_vertex[index] == v)return index;}assert(false);return -1;}void AddEdge(const V& v1, const V& v2, const W& weight){// 获取v1和v2顶点在顶点集合中的下标size_t srcIdx = GetIndexOfVertex(v1);size_t dstIdx = GetIndexOfVertex(v2);if (srcIdx == dstIdx)return;_edges[srcIdx][dstIdx] = weight;//如果是无向图,则A-B,对应的B-A的权值也应该更新if (!IsDirect)_edges[dstIdx][srcIdx] = weight;}size_t GetDevOfVertex(const V& v){size_t devCount = 0;size_t vIdx = GetIndexOfVertex(v);size_t N = _vertex.size();// 出度for (size_t i = 0; i < N; ++i){if (_edges[vIdx][i] != _invalidWeight)devCount++;}if (IsDirect){// 入度for (size_t i = 0; i < N; ++i){if (_edges[i][vIdx] != _invalidWeight)devCount++;}}return devCount;}void PrintGraph(){// 打印顶点for (auto e : _vertex)cout << e << "  ";cout << endl;// 打印矩阵中的内容size_t N = _vertex.size();for (size_t i = 0; i < N; ++i){for (size_t j = 0; j < N; ++j){cout << _edges[i][j] << " ";}cout << endl;}cout << endl;}
private:std::vector<V> _vertex;std::vector<vector<W>> _edges;W _invalidWeight;
};/*
测试用例:
"ABCDE"
'A', 'D' , 10
'A', 'E' , 20
'B', 'C' , 10
'B', 'D' , 20
'B', 'E' , 30
'C', 'E' , 40
*/void TestGraph()
{char* pstr = "ABCDE";Graph<char, int, true> g(pstr, strlen(pstr), -1);g.AddEdge('A', 'D', 10);g.AddEdge('A', 'E', 20);g.AddEdge('B', 'C', 10);g.AddEdge('B', 'D', 20);g.AddEdge('B', 'E', 30);g.AddEdge('C', 'E', 40);g.PrintGraph();cout << g.GetDevOfVertex('E') << endl;
}
  1. 邻接表
    邻接表:使用数组表示顶点的集合,使用链表表示边的关系。

1.无向图邻接表存储

2.有向图邻接表存储

代码实现:

template<class W>p
struct LinkEdge
{LinkEdge<W>* _pNext;
W _weight; // 节点的权值
size_t _src; // 起点的下标(后面使用)
size_t _dst; // 终点的下标
LinkEdge(size_t src, size_t dst, const W& weight)
: _src(src)
, _dst(dst)
, _weight(weight)
, _pNext(NULL)
{}
};
template<class V, class W, bool IsDirect = false>
class Graph
{typedef LinkEdge<W> LinkEdge;typedef Graph<V, W, IsDirect> Self;
public:Graph(const V* array, size_t size): _v(array, array+size)
{_linkEdges.resize(size);
}// g.AddEdge('A', 'D', 10);
void AddEdge(const V& v1, const V& v2, const W& weight)
{size_t src = GetIndexOfV(v1);size_t dst = GetIndexOfV(v2);_AddEdge(src, dst, weight);if(!IsDirect)_AddEdge(dst, src, weight);
}// 获取顶点元素在其数组中的下标size_t GetIndexOfV(const V& v)
{for(size_t i = 0; i < _v.size(); ++i)
{if(v == _v[i])return i;
}assert(false);return -1;
}void PrintGraph()
{for(size_t index = 0; index < _v.size(); ++index)
{cout<<"v["<<_v[index]<<"]--->";LinkEdge* pCur = _linkEdges[index];while(pCur)
{cout<<"v["<<_v[pCur->_dst]<<"]--->";pCur = pCur->_pNext;
}cout<<"NULL"<<endl;
}
}//获取节点的度
int GetVDev(const V& v)
{size_t index = GetIndexOfV(v);    LinkEdge* pCur =_linkEdges[index];size_t count = 0;
// 出度
while(pCur)
{count++;pCur = pCur->_pNext;
}if(IsDirect)
{// 入度int dst = index;for(size_t src = 0; src < _v.size(); ++src)
{if(src == dst)continue;else
{LinkEdge* pCur = _linkEdges[src];while(pCur)
{if(pCur->_dst == dst)count++;pCur = pCur->_pNext;
}
}}
}return count;
}void _AddEdge(size_t src, size_t dst, const W& weight)
{LinkEdge* pCur = _linkEdges[src];// 检测当前边是否存在while(pCur){if(pCur->_dst == dst)return;pCur = pCur->_pNext;
}//头插LinkEdge* pNewNode = new LinkEdge(src, dst, weight);pNewNode->_pNext = _linkEdges[src];_linkEdges[src] = pNewNode;
}private:vector<V> _v;vector<LinkEdge*> _linkEdges;
};

3.图的遍历

1.广度优先遍历

类比二叉树的层序遍历;

void level_order(Bitree *root)
{if(root==NULL)return;Bitree p=root;queue<Bitree>queue_;queue.push(p);while(!queue_.empty()){p=queue_.front();cout<<p->data<<endl;queue_.pop();    if(p->lchild)queue_.push(p->lchild);if(p->rchild)queue_.push(p->rchild);}}

图的广度优先遍历

_BFS(queue<int>& q,vector<bool>& visted){while(!q.empty()){size_t index=q.front;
if(!visted[index])  //如果该节点没有遍历过
{cout<<_v[index]<<":";visted[index]=true;LinkEdge* pCur = _linkEdges[index];while(pCur){q.push(pCur->_dst);pCur = pCur->_pNext;
}
}q.pop();}cout<<endl;
}void BFS(const V& v){queue<int>q;vector<bool>_visted(_v.size(),false);q.push(GetIndex(v));_BFS(q,_visted);}

2.深度优先遍历
类比二叉树的先序遍历

1 void preOrder1(BinTree *root)     //递归前序遍历
2 {3     if(root!=NULL)
4     {5         cout<<root->data<<" ";
6         preOrder1(root->lchild);
7         preOrder1(root->rchild);
8     }
9 }

图的深度遍历


void _DFS(int index, vector<bool>& visited)
{if(!visited[index]){cout<<_v[index]<<" ";visited[index] = true;LinkEdge* pCur = _linkEdges[index];while(pCur){_DFS(pCur->_dst, visited);pCur = pCur->_pNext;}}
}void DFS(const V& v)
{cout<<"DFS:";vector<bool> visited(_v.size(), false);_DFS(GetIndexOfV(v), visited);for(size_t index = 0; index < _v.size(); ++index)_DFS(index, visited);cout<<endl;

数据结构——“图”的相关知识总结相关推荐

  1. 系统开发基础:UML中图的相关知识笔记(下)

    1.状态图 状态图展现了一个状态机,它由状态.转换.事件.活动组成.状态图关注系统的动态视图,它对于接口.类.协作的行为建模尤为重要,它强调对象行为的事件顺序. 组成:简单状态.组合状态.转换(事件和 ...

  2. 系统开发基础:UML中图的相关知识笔记(上)

    1.图的概念 图(Diagram) 是一组元素的图形表示,大多数情况下,把图画成顶点(代表事物)和弧(表示关系)的连通图. 2.UML中图的分类 UML2.0中的图主要有:类图.对象图.用例图.序列图 ...

  3. 【数据结构】List相关知识的学习【详解篇2】

    文章目录 List 泛型(Generic)的基本介绍 泛型的使用 泛型总结 包装类(Wrapper Class) 基本数据类型和包装类直接的对应关系 包装类的使用:装箱(boxing)和拆箱(unbo ...

  4. Gantt图和PERT图的相关知识

    1.Gantt 图 Gantt图以时间为基准描述项目任务,可以清晰的描述每个任务从何时开始,到何时结束,以及每个任务的并行关系,但是不能反映项目各任务之间的依赖关系,也无法确定整个任务的关键所在. 2 ...

  5. 算法笔记(七)—— 图的相关知识及算法

    图的存储方式 1. 邻接表(记录关于某点的直接相邻点) 2. 邻接矩阵(一定是正方形的矩阵,对点进行编号,点到点的权值由距震中的值表示,无直接相连记为正无穷) 图的模板 unordered_map&l ...

  6. (转载)技术族谱:软件开发相关知识体系的整理心得(图)

    每隔一段时间,就会收到些类似的消息: 怎么学好软件开发? 我已经学完了A,我接下来该学B还是C? 其实这样的问题,真的是一言难复.如何学习,是一个很复杂的话题,尤其是眼下业内的技术名词日新月异,乱花迷 ...

  7. 算法基础:图的相关算法知识笔记

    一.图的相关算法 1.图的分类知识 如下图: 2.生成树概念 对连通图进行遍历,过程中所经过的边和顶点的组合可看做是一棵普通树,通常称为生成树. 连通图的生成树具有这样的特征:边的数量 = 顶点数 - ...

  8. 图神经网络与图注意力网络相关知识概述

    #图神经网络# #图注意力网络# 随着计算机行业和互联网时代的不断发展与进步,图神经网络已经成为人工智能和大数据的重要研究领域.图神经网络是对相邻节点间信息的传播和聚合的重要技术,可以有效地将深度学习 ...

  9. Redis 九种数据结构及其底层实现 持久化 缓存机制 过期键与内存淘汰 集群等相关知识

    参考内容: B站尚硅谷Redis视频教程 <Redis 6 入门到精通 超详细 教程> B张黑马程序员Redis视频教程 <黑马程序员Redis入门到实战教程,全面透析redis底层 ...

最新文章

  1. php内外边距,选择器与内外边距使用方法(margin,padding使用)-2019年9月4日
  2. Hibernate映射关系
  3. python获取系统硬件信息
  4. Java从网络批量读取图片并保存至本网站服务器后再插入文章中
  5. JAVA之旅(五)——this,static,关键字,main函数,封装工具类,生成javadoc说明书,静态代码块...
  6. Kubernetes 配置私有镜像仓库时,没有权限访问的问题
  7. Atitit 搜索的艺术 目录 1. 索引基础 2 1.1. 单词-文档矩阵 2 1.2. 倒排索引基本概念 3 2. 建立索引 4 2.1. 两遍文档遍历法(2-Pass In-Memory In
  8. 第14章Stata因变量受限回归分析
  9. osgb倾斜模型顶层合并
  10. linux下更新pip3
  11. ORACLE 锁解释
  12. Unity中配合EmmyLua的Lua使用方案
  13. SaaS已死。下一个。
  14. 【网络】Padavan 路由器固件开启教育网 IPv6
  15. 如何科学的建立自己的个人网站
  16. 《袁老师访谈录》第五期 | 史维教授/香港科大校长:【与香港科大一起群飞得更远!】...
  17. Google I/O 2021: 在重要时刻提供帮助
  18. paas平台_paas平台排名
  19. SLD各地物配置文件
  20. python易盾滑动验证码

热门文章

  1. 【语义分割】——FCN测试
  2. 【WinForm】DataGridView单元格居中
  3. 勒索病毒资料(腾讯管家整理)
  4. c++ ----to_string、stoi()、atoi()
  5. vscode import 自动引入文件路径
  6. 第五天:全国计算机等级考试C语言专题
  7. 基于 J a v a S c r i p t 与 D B G R I D控件的 B / S结构客户端联想式录入技术的设计与实现
  8. 华为FusionStorage分布式存储介绍及组件关系
  9. AQR资本20年精选20篇之:风格因子工匠精神Alpha
  10. Tomcat异常:The ResourceConfig instance does not contain any root resource classes