按照边和顶点的个数(顶点数$n$,边数$m$),图可以分为两大类:稀疏图($m$没有达到$n^2$级别),稠密图($m$达到$n^2$级别)。
邻接链表空间复杂度为$O(m+n)$,邻接矩阵空间复杂度为$O(n^2)$;为了使得空间复杂度更优,稀疏图通常采用邻接链表,稠密图通常采用邻接矩阵。
图的邻接链表即每个顶点一个邻接链表(一共n
个邻接链表),存储的是与这个顶点邻接的所有顶点(n
个邻接链表结点数之和等于图的边数)。
算法题中,一般采用静态链表实现,链表结点j
的值value[j]
存储边j
的指向顶点的编号,next[j]
存储链表结点j
的next
指针,head[i]
存储顶点i
的邻接链表的首指针,d
表示数组已用位的下一位;
int value[m], next[m], head[n], d = 0;
memset(head, -1, sizeof(head));
如果要存储权值,可用node[i]
存储顶点i的权值,edge[j]
存储边j的权值;
int node[n], edge[m];
添加一条a到b的边:
value[d] = b;
next[d] = head[a];
head[a] = d++;
顶点个数为n;
graph[i][j]
存储顶点i到顶点j的边的权值,权值为正无穷大表示不可达;
int graph[n][n];
memset(graph, 0x3f, sizeof(graph));
for (int i = 0; i < n; i++) graph[i][i] = 0;
如果要存储顶点权值,可用node[i]
存储顶点i的权值;
int node[n];
树可以和图使用相同的存储方法,除此之外还有一些特有的存储方法。
K叉树孩子个数固定为k
个(比如二叉树),使用二维数组tree
存储树,tree[i][j]
存储结点i
的第j
个孩子的编号;
int tree[n][k];
如果要存储权值,可用node[i]
存储结点i的权值,edge[i][j]
存储结点i到结点tree[i][j]
的边的权值;
int node[n], edge[n][k];
结点编号从1
开始,tree[1...n]
中的tree[i]
存储结点i
的权值,tree[0]
空缺;
若2i
$\leqslant$ n
,结点i
有左子结点为2i
,若2i+1
$\leqslant$ n
,结点i
有右子结点为2i+1
;
若i/2
$\geqslant$ 1
,结点i
有父结点为i/2
;
int tree[n+1];
如果要存储边权值,可用edge[i]
存储结点i
的父结点i/2
到结点i
的边的权值,结点0
和1
没有父结点,edge[0]
、edge[1]
空缺;
int edge[n+1];
注解:完全二叉树之所以结点编号从1
开始,一是子结点和父结点的编号的计算公式方便记忆,二是可以用i/2
$\geqslant$ 1
来判断有父结点,与判断是否具有子结点的方式十分相似,如果编号从0
开始却不能用(i-1)/2
$\geqslant$ 0
来判断。