树和图的存储结构

按照边和顶点的个数(顶点数$n$,边数$m$),图可以分为两大类:稀疏图($m$没有达到$n^2$级别),稠密图($m$达到$n^2$级别)。

邻接链表空间复杂度为$O(m+n)$,邻接矩阵空间复杂度为$O(n^2)$;为了使得空间复杂度更优,稀疏图通常采用邻接链表,稠密图通常采用邻接矩阵。

邻接链表

图的邻接链表即每个顶点一个邻接链表(一共n个邻接链表),存储的是与这个顶点邻接的所有顶点(n个邻接链表结点数之和等于图的边数)。

算法题中,一般采用静态链表实现,链表结点j的值value[j]存储边j的指向顶点的编号,next[j]存储链表结点jnext指针,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叉树孩子个数固定为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的边的权值,结点01没有父结点,edge[0]edge[1]空缺;

int edge[n+1];

注解:完全二叉树之所以结点编号从1开始,一是子结点和父结点的编号的计算公式方便记忆,二是可以用i/2 $\geqslant$ 1来判断有父结点,与判断是否具有子结点的方式十分相似,如果编号从0开始却不能用(i-1)/2 $\geqslant$ 0来判断。