一、图的基本知识
1、图的基本表示方法:G=(V,E)
2、图的分类
无向图
有向图
3、图的一些基本概念
完全图
在有n个顶点的无向图中,有(n-1)*n/2条边,任意两个顶点都有一条边。
在有n个顶点的有向图中,有(n-1)*n条边,任意两个顶点都有两条边。
邻接顶点
在无向图中,若(u,v)是E(G)中的一条边,则u,v互为邻接顶点,并称边(u,v)依附于顶点u,v。
在有向图中,若(u,v)是E(G)中的一条边,则称u邻接到v,v邻接自顶点u,称u,v相关联。
顶点的度
顶点与他相关联的边的条数,记作dev(v)
出度(indev):以v为终点的边。
入度(outdev):以v为起点的边。
在无向图中,顶点的出度,入度,度相同。dev(v)=indev(v)=outdev(v)。
在有向图中,顶点的度是出度于入度的和。dev(v)=indev(v)+outdev(v)。
路径
在图G(V,E)中,有一组边能从vi出发到vj,这组边构成的是路径。
路径长度
若边有权值,则长度是边的权值总和。
若边没有权值,则长度是边的个数。
简单路径与回路
简单路径
若路径上各顶点均不重复,则为简单路径。
回路
若简单路径的起点与终点重复就是回路。
子图
子图的边是父图的部分边,子图的顶点是父图的部分顶点。
连通图(无向图的专有概念)
在无向图中,从vi到vj有路径就称vi到vj连通。若任意一对顶点连通就是连通图。(只要图中没有孤岛就是连通图)
强制连通图(有向图专有概念)
在有向图中,每一对顶点vi,vj,从vi到vj有路径,从vj到vi也有路径,则称强制连通图。
生成树
一个连通图的最小连通子图是生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边。
最小生成树
权值最小的生成树。
二、邻接表表示图
1、邻接表结构
2、邻接表优势
适合存储稀疏图。
查找一个顶点相连的所有边。
3、邻接表代码实现
首先,我们需要用struct Edge来把边表示出来。
//模版的W指权值的数据类型
template<class W>
struct Edge
{
int _dsti; // 终点下标
W _w; // 权值大小
Edge<W> _next; // 链接指针
Edge(int dsti, const W& w)
:_dsti(dsti)
,_w(w)
,_next(nullptr)
{}
};
我们这里手动输入图中的节点和权值,所以Graph构造函数中要有顶点的集合和个数。
// 类型形参:V表示顶点数据类型,W表示权值数据类型
// 非类型形参:若权值不传参数就默认是INT_MAX,同理默认是无向图
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
typedef Edge<W> Edge;
public:
Graph(const V* a, int n)
{
_vertexs.reserve(n);
for(int i = 0; i < n; i++)
{
_vertexs.push_back(a[i]);
_indexmap[a[i]] = i;
}
_tables.resize(n, nullptr);
}
private:
vector<V> _vertexs; // 顶点数组
map<V, int> _indexmap; // 顶点下标对应
vector<Edge*> _tables; // 邻接表集合
}
邻接表中添加边操作
基本原理:
因为链表的结构,所以我们采用头插法。
第一步,要找到起点srci在_tables的位置,即_tables[srci]。
第二步,要new一个Edge,存终点和权值。
第三部,头插。
特别的,若是无向图由于对称性,我们还要将终点起点交换,再进行一次上述操作。
void AddEdge(const V& src, const V& dst, const W& w)
{
// 先去顶点下标
int srci = GetIndex(src);
int dsti = GetIndex(dst);
// src->dst
Edge* eg = new(dsti, w);
eg->next = _tables[srci];
_tables[srci] = eg;
// 若是无向图,还要交换一次
if(Direction == false)
{
Edge* eg = new(srci, w);
eg->next = _tables[dsti];
_tables[dsti] = eg;
}
}
三、邻接矩阵表示图
1、邻接矩阵结构
2、邻接矩阵优势
快速查找所有边的权值。
3、邻接矩阵代码实现
与邻接表成员函数类似,依然有_vertexs,_indexmap。但是要存矩阵所以是_matrix。
template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public:
Graph(const V* a, size_t n)
{
_vertexs.reserve(n);
for (int i = 0; i < n; i++)
{
//先创建顶点和映射关系
_vertexs.push_back(a[i]);
_indexmap[a[i]] = i;
}
//创建n*n矩阵
_matrix.resize(n);
for (int i = 0; i < n; i++)
{
//默认全是孤独的
_matrix[i].resize(n, MAX_W);
}
}
private:
vector<V> _vertexs; //顶点集合
map<V, int> _indexmap; //顶点的下标集合
vector<vector<W>> _matrix; //邻接矩阵表示边的集合
};
邻接矩阵中添加边操作
基本原理:
在_matrix[srci][dsti]中存的是src->dst边的权值,所以直接赋值即可。
注意无向图对称性 ,_matrix[dsti][srci]也要赋值。
void AddEdge(const V& src, const V& dst, const W& w)
{
size_t srci = GetVertexIndex(src);
size_t dsti = GetVertexIndex(dst);
_matrix[srci][dsti] = w;
//如果是无向图要添加两次权值
if (Direction == false)
{
_matrix[dsti][srci] = w;
}
}