线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图释“多对多”的关系。图是一种复杂的非线性的结构,图中的每个元素可以有零个或多个前驱,也可以有零个或多个后继,也就是说,元素之间的关系式任意的。
一、图的概念
定义:图其实是由顶点集合和顶点间的关系组成的一种数据结构。通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
图的分类
图可以分为无向图和有向图。
左图为无向图,是由顶点和边构成的,右图为有向图,是由顶点和弧构成,弧有弧头和弧尾的区别。
按照边可以将图分为稀疏图和稠密图,这是个模糊的概念,也是相对的。
(x,y)表示无向的边,
二、图的存储
1.领接矩阵
领接矩阵用两个数组保存数据。一个一维数组存储图顶点信息,一个二维数组存储图中的边或弧的信息。
在无向图中,二维数组是个对称矩阵。
特点:
0代表无边,1代表有边。
顶点的度是所在行数组之和。
求取顶点领接边,将行内元素遍历下。
有向图的领接矩阵,各行之和是出度,各列之和是入度。
带权的图称为网,用领接矩阵表示:
领接矩阵对于边数相对顶点较少的图,是很浪费空间的。
领接矩阵的优点&缺点
优点:连通
缺点:从顶点到某一点的路径不方便表示。
代码:
//领接矩阵存储
template <class V,class W,bool IsDirect=false>//默认情况下是无向图
class Graph
{
public:
Graph()
{}
Graph(const V* array,size_t size)
{
//构造顶点
_v.resize(size);
for (size_t i = 0; i < size; ++i)
{
_v[i] = array[i];
}
//动态开辟二维数组
_edges.resize(size);
for (size_t i = 0; i < size; ++i){
_edges[i].resize(size);
}
}
///获取顶点元素在其数组中的下标
size_t GetIndexOfV(const V& value)
{
int size = _v.size();
for (size_t i = 0; i < size; ++i){
if (_v[i] == value)
return i;
}
return -1;//返回-1表示找不到
}
//获取顶点的度
size_t GetDevOfV(const V& value)
{
int size = _v.size();
int dev = 0;
int index = GetIndexOfV(value);
if (index != -1)//该顶点存在
{
for (size_t i = 0; i < size; ++i){
if (_edges[index][i] != 0)//有边
++dev;
}
//有向图
if (IsDirect){
for (size_t j = 0; j < size; j++){
if (_edges[j][index] != 0)
++dev;
}
}
}
return dev;
}
//添加顶点v1和v2对应的权值为weight的边
void AddEdge(const V& v1, const V& v2, const W& weight)
{
int index1 = GetIndexOfV(v1);
int index2 = GetIndexOfV(v2);
if (index1 != -1 && index2 != -1)//两个顶点均存在
{
_edges[index1][index2] = weight;
//无向图
if (!IsDirect){
_edges[index2][index1] = weight;
}
}
}
//图的打印
void PrintGraph()
{
int size = _v.size();
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < size; ++j)
{
printf("%2d ", _edges[i][j]);
}
printf("\n");
}
printf("\n");
}
private:
vector<V> _v;//图的顶点
vector<vector<W>> _edges;//任意两个顶点之间的边的权值
};
测试代码:
void test1()
{
int array[] = { 0, 1, 2, 3 };
Graph<int, int> g(array, sizeof(array) / sizeof(array[0]));
g.AddEdge(0, 1, 10);
g.AddEdge(2, 1, 30);
g.AddEdge(1, 2, 20);
g.AddEdge(0, 3, 40);
g.GetDevOfV(1);
g.PrintGraph();
}
2.领接表
数组和链表结合的存储方法,顶点用一个一维数组存储,每个顶点vi的所有领接点构成一个线性表。
代码:
template<class W>
struct LinkEdge
{
size_t _src;//边的起点
size_t _dst;//边的终点
W _weight;//边的权值
LinkEdge* _pNext;//指向下一条边
LinkEdge()
{}
//有参构造函数
LinkEdge(const size_t& src, const 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> Node;
typedef Node* PNode;
public:
Graph()
{}
Graph(const V* array, size_t size)
:_v(array,array+size)
{
_linkEdges.resize(size);
}
//获取顶点元素在其数组中的下标
size_t GetIndexOfV(const V& value)
{
int size = _v.size();
int index = 0;
for (size_t i = 0; i < size; ++i)
{
if (_v[i] == value)
return i;
}
return -1;
}
//添加顶点为v1和v2所对应权值为weight的边(采用头插)
void AddEdge(const V& v1, const V& v2, const W& weight)
{
int index1 = GetIndexOfV(v1);//起点的下标
int index2 = GetIndexOfV(v2);//终点的下标
PNode pSrc = _linkEdges[index1];//起点
PNode pDst = _linkEdges[index2];//终点
if (index1 != -1 && index2 != -1)//顶点均存在
{
PNode newNode =new Node(index1, index2, weight);
newNode->_pNext = pSrc;
_linkEdges[index1] = newNode;
if (!IsDirect)//无向图
{
PNode newNode =new Node(index2, index1, weight);
newNode->_pNext = pDst;
_linkEdges[index2] = newNode;
}
}
}
//获取顶点的度
int GetVDev(const V& value)
{
int dev = 0;
int index = GetIndexOfV(value);
PNode pCur= _linkEdges[index];
while (pCur)
{
++dev;
pCur = pCur->_pNext;
}
return dev;
}
void PrintGraph()
{
int size = _v.size();
for (size_t i = 0; i < size; ++i)
{
PNode pCur = _linkEdges[i];
while (pCur)
{
cout << _v[pCur->_src] << "--->" << _v[pCur->_dst] <<" 权值"<< pCur->_weight<<endl;
pCur = pCur->_pNext;
}
}
}
//获取边的权值
W GetVWeight(const V& v1,const V& v2)
{
int index1 = GetIndexOfV(v1);//起点的下标
int index2 = GetIndexOfV(v2);//终点的下标
if (index1 != -1 && index2 != -1)
{
PNode pCur = _linkEdges[index1];
while (pCur)
{
if (pCur->_dst == index2)
return pCur->_weight;
pCur = pCur->_pNext;
}
}
}
private:
vector<V> _v;//存储图的顶点
vector<PNode> _linkEdges;//存储边,结点
};
测试代码:
void test2()
{
int array[] = { 0, 1, 2, 3 };
Graph<int, int> g(array, sizeof(array) / sizeof(array[0]));
g.AddEdge(0, 1, 10);
g.AddEdge(0, 2, 20);
g.AddEdge(1, 2, 30);
g.AddEdge(2, 1, 40);
g.AddEdge(3, 2, 50);
cout<<"以0为顶点的度为:"<< g.GetVDev(0)<<endl;
g.PrintGraph();
cout<<"0,1顶点的边的权值为:"<<g.GetVWeight(0, 1)<<endl;
}