按照边和顶点的个数(顶点数$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来判断。