图是一种很常用的数据结构,其算法的难度相比其他数据结构,难度更大一些,本编文章,将对图的一些基本概念和存储做一些讲解。
图是由顶点集(V)和边集(E)组成,记为 G = (V,E) ; 其中,顶点集不能为空,边集可以为空。图分为以下两种:
1. 有向图
若 E 是有向边(弧)的有限集合时,则图 G 为有向图。其中,弧是顶点的有序对。
2. 无向图
若 E 是无向边(边)的有限集合时,则图 G 为无向图。其中,边是顶点的无序对。
通过以上,明确了图的基本定义,下面讨论图中一些重要的概念:
1.图中顶点的度,入度和出度
顶点的度的定义:以该定点为一个端点的边的数目
对于无向图,定点 V 的度是指依附于该定点的边的条数,记为 TD(V)。在具有 n 个定点 e 条边的无向图中,有 ∑TD(v) = 2e, 即无向图的全部定点的度之和等于边数的两倍。
对于有向图,定点 v 的度分为入度和出度。其中,有向图的全部定点的入度之和与出度之和相等,且等于边数。
其他一些概念在此不做详述。以下主要就图的存储方式给以说明:
图的存储必须要完整,准确的反应顶点集和边集的信息,根据不同图的结构和算法,可以采用不同的存储方式,而且不同的存储方式将对算法的效率产生相当大的影响。无论是有向图还是无向图,主要的存储方式有两种:邻接矩阵(顺序存储)和邻接表(链接存储)。
1. 邻接矩阵
所谓的邻接矩阵,就是用一个一维数组存储图中定点的信息,用一个二维数组存储图中边的信息(各定点的邻接关系),存储定点之间邻接关系的二维数组称为邻接矩阵。邻接矩阵式一个 n*n 的方阵(n 表示点的数目)。
对于无权图而言,其二维数组中的定义为:
- A[i][j] = 1 若 ( v[i], v[j] )是 E (G) 中的边
- A[i][j] = 0 若 ( v[i], v[j] )不是E(G) 中的边
注意,其对于无向图而言,是一个对称的矩阵。
用邻接矩阵存储时,很容易获得任意两个顶点时间是否右边相连接,但是,要确定图中有多少边,需按行,列对每个元素进行遍历,花费代价较大。
2. 临接表法
当一个图是稀疏图时,使用邻接矩阵会浪费较大的空间,而临界表结合了顺序存储和链式存储,大大减少了不必要的空间浪费。
邻接表就是对图的每个顶点建立一个单链表,第 i 个单链表中的节点表示依附于顶点 V(i) 的边,每个元素都是由顶点域和指针构成,下面给出其定义:
struct node
{
node():next(nullptr){}
int val;
struct node *next;
};
void buildLink(const int (*v)[5], node link[])
{
for( int i = 0; i < sz; ++i )
{
link[i].val = i;
}
for( int i = 0; i < sz; ++i )
{
node *t = &link[i];
for( int j = 0; j < sz; ++j )
{
if( v[i][j] != 0 )
{
node *s = new node();
s->val = j;
t->next = s;
t = s;
}
}
}
}
通过以上代码可以完成对邻接矩阵存储的图到邻接表存储的转换。
对图的存储还有:十字链表以及邻接多重表的方法,这两个存储方式使用较少,在这里不做描述。