一、单链表
1. 存储方式(不总结结构体存储)
单链表的存储,主要是以下几个方面:
- 存储每个点的值
e[i]
- 存储每个结点的下一个结点
ne[i]
- 存储头结点
head
用这3个变量去模拟链表。其中每个结点用数组下标去索引,即每个点对应一个下标。相应的,ne
指针就指向下一个结点的下标,而非地址。
/**
* head : 指向 头结点的下标
* e[i] : 下标为i的点的值
* ne[i] : 下标为i的点 的 下一个结点的下标
* idx : 结点个数。[0, idx)的每个数对应一个结点
*/
int head, e[N], ne[N], idx;
2. 操作
(1)初始化init()
链表初始化,即链表为空。头结点指针head
指向-1,结点个数idx
= 0。
void init()
{
head = -1;
idx = 0;
}
(2)插入结点:插入表头
可以看到,操作主要分两步:
- 新结点的
ne
指向当前头结点 head
指向新结点
/**
* x : 插入的结点值
*/
void add_to_head(int x)
{
e[idx] = x; // 1. 存储节点的值
ne[idx] = head; // 2. 新结点ne指向当前头结点
head = idx; // 3. head指向新结点
idx ++ ; // 4. 节点个数+1
/* idx实际上记录了每个点是第几个输入的点 */
}
(3)插入结点:在输入的第k个点后面插入
前面说到,idx
记录了每个点是第几个输入的。所以对于5
来说,idx=1
。
void add(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx ++ ;
}
(4)删除第k个输入的结点
void remove(int k)
{
ne[k] = ne[ne[k]];
}
二、树、图的存储
由于树是特殊的图,所以只讨论图的存储。
对于图来说,有两种存储方式:
- 邻接矩阵
- 邻接表
下面主要说明邻接表的存储方式。邻接表是指:对于图上的每个结点来说,用一个单链表存储它的邻接点,如下图所示:
所以,与单链表的存储方式相比,不同点在于:有很多头结点,所以head
是一个数组。对于无权图来说,代码如下:
/**
* h[i] : 指向头结点i
* e[i] : 第i个点的值
* ne[i] : 第i个点的下一个结点
* idx : 节点个数
*
* N : 结点个数
* M : 2 * N, 因为无向图会插入两次
*/
int h[N], e[M], ne[M], idx;
void init() // 初始化邻接表
{
memset(h, -1, sizeof(h));
idx = 0;
}
void add(int a, int b) // 在a的后面插入b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int main()
{
...
// 遍历图
// u为根结点
for(int i = h[u]; i != -1; i = ne[i])
{
...
}
}
对于有权图来说,代码如下:
/**
* h[i] : 指向头结点i
* e[i] : 第i个点的值
* ne[i] : 第i个点的下一个结点
* idx : 节点个数。同时也指明了插入的第几个点,插入的第几条边
*
* N : 结点个数
* M : 2 * N, 因为无向图会插入两次
*/
int h[N], w[M], e[M], ne[M], idx; /* 加了一个w[M], 表示每条边的权重 */
void init() // 初始化邻接表
{
memset(h, -1, sizeof(h));
idx = 0;
}
void add(int a, int b, int c) // 在a的后面插入b, 边权为c
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int main()
{
...
// 遍历图
// u为根结点
for(int i = h[u]; i != -1; i = ne[i])
{
...
}
}