题外话: 呀,又偷懒了半天,(๑><๑)罪恶罪恶。。。
PS: 本文的重点是代码代码代码!而不是数据结构的一些基础概念和分类;
完全没接触过的同学,可以去看B站王卓教授的视频。
1.什么是图?
- 官话: 顶点、及连接顶点之间边的集合(顶点和边的集合)。
- 我的理解:
- 一对一是线性结构,一对多是树,多对多是图。
- 图的概念比较大,你可以把树看做是一种特殊的图。
2.为什么要有图?或者说有什么应用?
- 场景1:高德地图导航
- 每个小区、商铺、饭店、景点等可作为出发地和目的地的,就是图的顶点;
- 这些顶点,现实中由一条条马路连接而成,这些马路就是边;
- 马路的公里数、通过时间就是边的权重;
- 上述的元素构成了一个图,计算从家到公司到底该走哪条路公里数最短or时间最短,就是图的应用。
- 场景2:飞猪买飞机票
- 全国各大有机场的城市,可以视为图的顶点;
- 两个城市之间的航线(直达),可以视为边;
- 各个航线的机票价格、航行时间可以视为边的权重;
- 那么!!飞猪给你推荐从北京到上海坐哪个航班最便宜、怎么转机最便宜、做哪个航班时间最短,就是图的应用。
3.图按边是否有方向分类
- 有向图:
- 顾名思义,边带有方向的图;
- 有方向就意味着两个顶点之间的关系,是具有方向性的;
- 比如说A喜欢B,但B不喜欢A,放到图里面,可以理解为A->B,但B无法通向A。
- 无向图:
- 边没有方向的图;
- 只要两个顶点连上了,就意味着两个顶点可以相互触达。
4.图按存储结构分类
- 邻接矩阵实现图
- 邻接矩阵我的理解:
- 首先,一共n个顶点都保存在了数组中,每个顶点都有一个索引对吧;
- 然后,建立一个n x n的二维数组,行/列都对应了顶点的索引也没问题吧;
- 最后,以行,列的顺序去找到对应二维数组的值,是1就代表两个索引的顶点连通,是0则不连通,多好理解。
- 有向图和无向图的邻接矩阵
- 邻接矩阵我的理解:
- 邻接表实现图(链表)
- 邻接表的有向和无向图
- 邻接表的有向和无向图
5.邻接矩阵图的代码实现
namespace YoyoCode
{
/// <summary>
/// 邻接矩阵实现的图(默认按照有向图,弧不带权重)
/// </summary>
internal class MatrixGraph<T> where T : class
{
//最大顶点数量
public int MaxVertexNum;
//存储顶点的数组
public T[] Vertex;
//邻接矩阵
public short[,] AdjacecntMatrix;
//顶点数量
public int Count = 0;
//遍历时该节点是否访问过
private bool[] _visited;
/// <summary>
/// 自定义大小的矩阵
/// </summary>
/// <param name="MaxSize"></param>
public MatrixGraph(int MaxSize)
{
MaxVertexNum = MaxSize;
Vertex = new T[MaxVertexNum];
AdjacecntMatrix = new short[MaxVertexNum, MaxVertexNum];
}
/// <summary>
/// 增加顶点
/// </summary>
public void AddVertex(T vertex)
{
if (Count >= MaxVertexNum)
{
throw (new OverflowException("已达到数组最大可容纳顶点数!"));
}
Vertex[Count++] = vertex;
}
/// <summary>
/// 按照顶点的索引添加边
/// </summary>
/// <param name="m">出度顶点的索引</param>
/// <param name="n">入度顶点的索引</param>
/// <param name="isMutual">该边是否是双向的</param>
public void AddEdge(int m, int n, bool isMutual = false)
{
//判断顶点是否存在
if (Vertex[m] == null || Vertex[n] == null)
{
throw (new IndexOutOfRangeException("输入的边对应的顶点不存在!"));
}
AdjacecntMatrix[m, n] = 1;
if (isMutual)
{
AdjacecntMatrix[n, m] = 1;
}
}
/// <summary>
/// 图是否为空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return Count == 0;
}
/// <summary>
/// 清空图
/// </summary>
public void Clear()
{
for (int i = 0; i < Count; i++)
{
Vertex[i] = null;
}
for (int i = 0; i < Count; i++)
{
for (int j = 0; j < Count; i++)
{
AdjacecntMatrix[i, j] = 0;
}
}
}
/// <summary>
/// 深度优先遍历
/// </summary>
/// <param name="myAct">遍历时对每个元素的处理函数</param>
/// <param name="idx">当前顶点的索引</param>
/// <param name="firstIn">是否为第一次递归,影响到辅助数组的初始化</param>
public void DFS(Action<T> myAct,int idx, bool firstIn = false)
{
//初始化辅助数组
if (firstIn)
{
_visited = new bool[Count];
}
_visited[idx] = true;
myAct(Vertex[idx]);
for (int i = 0; i < Count; i++)
{
if (AdjacecntMatrix[idx, i]!= 0 && !_visited[i])
{
DFS(myAct, i);
}
}
}
/// <summary>
/// 广度优先遍历
/// </summary>
public void BFS(Action<T> myAct, int idx)
{
//类似层次遍历,所以用队列
Queue<int> myQueue = new Queue<int>();
myQueue.Enqueue(idx);
//初始化辅助数组
_visited = new bool[Count];
_visited[idx] = true;
while (myQueue.Count != 0)
{
int curIdx = myQueue.Dequeue();
myAct(Vertex[curIdx]);
//邻接点入队
for (int i = 0; i < Count; i++)
{
if (AdjacecntMatrix[curIdx, i]!= 0 && !_visited[i])
{
myQueue.Enqueue(i);
_visited[i] = true;
}
}
}
}
}
}
- 测试代码及结果
说明: DFS是深度优先遍历,BFS是广度优先遍历;这两种遍历算法是图最重要的遍历算法。
6.邻接表的图代码实现
namespace YoyoCode
{
internal class ListGraph<T>
{
//最大顶点数量
public int MaxVertexNum;
//存储顶点的数组
public ListGraphVertex<T>?[] Vertex;
//顶点数量
public int Count = 0;
//遍历时该节点是否访问过
private bool[] _visited;
/// <summary>
/// 自定义大小的数组,用于存放顶点
/// </summary>
/// <param name="MaxSize"></param>
public ListGraph(int MaxSize)
{
MaxVertexNum = MaxSize;
Vertex = new ListGraphVertex<T>[MaxVertexNum];
}
/// <summary>
/// 增加顶点
/// </summary>
public void AddVertex(T value)
{
if (Count >= MaxVertexNum)
{
throw (new OverflowException("已达到数组最大可容纳顶点数!"));
}
Vertex[Count++] = new ListGraphVertex<T>(value);
}
/// <summary>
/// 对外的添加边的函数
/// </summary>
/// <param name="fromIdx"></param>
/// <param name="toIdx"></param>
/// <param name="isMutual"></param>
public void AddEdge(int fromIdx, int toIdx, bool isMutual = false)
{
//判断顶点是否存在
if (Vertex[fromIdx] == null || Vertex[toIdx] == null)
{
throw (new IndexOutOfRangeException("输入的边对应的顶点不存在!"));
}
InternalAddEdge(fromIdx, toIdx);
//一次输入,添加双向的两条边
if (isMutual)
{
InternalAddEdge(toIdx, fromIdx);
}
}
/// <summary>
/// 对内的添加边的函数,为了防止代码重复而写的
/// </summary>
/// <param name="fromIdx"></param>
/// <param name="toIdx"></param>
private void InternalAddEdge(int fromIdx, int toIdx)
{
//顶点是否已经有一条边
if (Vertex[fromIdx].Edge == null)
{
Vertex[fromIdx].Edge = new ListGraphEdge(toIdx);
return;
}
//其他情况
var curEdg = Vertex[fromIdx].Edge;
while (curEdg.NextEdge != null)
{
curEdg = curEdg.NextEdge;
}
curEdg.NextEdge = new ListGraphEdge(toIdx);
}
/// <summary>
/// 图是否为空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return Count == 0;
}
/// <summary>
/// 清空图
/// </summary>
public void Clear()
{
for (int i = 0; i < Count; i++)
{
Vertex[i] = null;
}
Count = 0;
}
/// <summary>
/// 深度优先遍历
/// </summary>
/// <param name="myAct">遍历时对每个元素的处理函数</param>
/// <param name="idx">当前顶点的索引</param>
/// <param name="firstIn">是否为第一次递归,影响到辅助数组的初始化</param>
public void DFS(Action<T> myAct, int idx, bool firstIn = false)
{
//初始化辅助数组
if (firstIn)
{
_visited = new bool[Count];
}
_visited[idx] = true;
myAct(Vertex[idx].Value);
var curEdg = Vertex[idx].Edge;
while (curEdg != null)
{
if (!_visited[curEdg.VertexIdx])
{
DFS(myAct, curEdg.VertexIdx);
}
curEdg = curEdg.NextEdge;
}
}
/// <summary>
/// 广度优先遍历
/// </summary>
public void BFS(Action<T> myAct, int idx)
{
//类似层次遍历,所以用队列
Queue<int> myQueue = new Queue<int>();
myQueue.Enqueue(idx);
//初始化辅助数组
_visited = new bool[Count];
_visited[idx] = true;
//邻接点入队
while (myQueue.Count != 0)
{
int curIdx = myQueue.Dequeue();
myAct(Vertex[curIdx].Value);
var curEdg = Vertex[curIdx].Edge;
while (curEdg != null)
{
if (!_visited[curEdg.VertexIdx])
{
myQueue.Enqueue(curEdg.VertexIdx);
_visited[curEdg.VertexIdx] = true;
}
curEdg = curEdg.NextEdge;
}
}
}
}
/// <summary>
/// 顶点
/// </summary>
internal class ListGraphVertex<T>
{
public T Value {
get; set; }
public ListGraphEdge? Edge {
get; set; }
public ListGraphVertex(T data)
{
Value = data;
}
}
/// <summary>
/// 边
/// </summary>
internal class ListGraphEdge
{
public int VertexIdx;
public ListGraphEdge? NextEdge {
get; set; }
public ListGraphEdge(int idx)
{
VertexIdx = idx;
}
}
}
结语: 写完了,至此常用的一些基本数据结构(顺序表、链表、顺序栈、链栈、顺序循环队列、链队、顺序结构二叉树、二叉链表二叉树、邻接矩阵图、邻接表图),我都以代码的形式展现出来啦~
后面会开始肝排序算法,噶油~~