链表与邻接表
【一般实现方式:指针+结构体——动态链表(工程中常用)】
1 2 3 4 5 6 7
| struct Node { int val; Node *next; }
new Node();
|
这里介绍用数组模拟【静态链表】该数据结构的方式——速度快!
单链表
单链表中用的最多的是邻接表——n个单链表:将每个点(共有n个点)的所有邻边存下来
邻接表应用:存储图和树(e.g. 最短路问题、最小生成树问题、……)
单链表基本结构:从头指针开始依次指向若干个节点,每个节点存储一个数值val和一个next指针,沿着next指针不断访问后续数据,直到指向空指针

数组模拟链表存储需要定义(均为整数型数组):
- e[N]:存储各点数值val
- ne[N]:存储各点next指针——空节点下标用-1表示
二者通过下标关联 :

事实上,同一下标i对应的e[i]与ne[i]表示一个节点的数值val及其next指针,将其看作一个结构体会更好理解,但在代码实现过程中,为使得代码尽量简洁,选择直接对数组下标进行操作
1 2 3 4 5 6 7 8 9 10 11 12
| struct Node { int e, ne; }nodes[N];
void remove(int k) { nodes[k].ne = nodes[nodes[k].ne].ne; }
|
需要注意:一个下标代表一个节点,各节点之间的指向关系仅由next指针(即ne[i]的值)决定
单链表的性质:顺序(单向)访问——可以在O(1)的时间复杂度内找到指定节点的后一个节点(包括完成插入等操作),但如果要找到指定节点之前的节点,则只能从头指针head开始遍历
例题:826. 单链表 - AcWing题库
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k 个插入的数后面的一个数;
- 在第 k 个插入的数后插入一个数。
现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 x。
D k
,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
I k x
,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
1≤M≤100000 所有操作保证合法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| #include <iostream>
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
void init() { head = -1; idx = 0; }
void add_to_head(int x) { e [idx] = x; ne [idx] = head; head = idx; idx ++ ; }
void add(int k, int x) { e [idx] = x; ne [idx] = ne[k]; ne[k] = idx; idx ++ ; }
void remove(int k) { ne[k] = ne[ne[k]]; }
int main() { int M; cin >> M; init(); char flag; int k, x; while (M -- ) { cin >> flag; if (flag == 'H') { cin >> x; add_to_head(x); } else if (flag == 'D') { cin >> k; if (k != 0) remove(k - 1); else head = ne[head]; } else if (flag == 'I') { cin >> k >> x; add(k - 1, x); } } int ptr = head; while (ptr != -1) { cout << e[ptr] << " "; ptr = ne[ptr]; } return 0; }
|
双链表
用于优化某些问题
基本结构与单链表一致,但每个节点会存储两个指针,一个指向前一个节点,另一个指向后一个节点
每个节点(下标)的指针存储方式:数组ne[] -> 指向前一个节点的指针数组l[] + 指向后一个节点的指针数组r[]
双链表中不再定义头节点head与尾节点tail,而是直接令0号下标对应的节点为head节点、令1号下标对应的节点为tail节点
例题:827. 双链表 - AcWing题库
实现一个双链表,双链表初始为空,支持 5 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 k 个插入的数删除;
- 在第 k 个插入的数左侧插入一个数;
- 在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
L x
,表示在链表的最左端插入数 x。
R x
,表示在链表的最右端插入数 x。
D k
,表示将第 k 个插入的数删除。
IL k x
,表示在第 k 个插入的数左侧插入一个数。
IR k x
,表示在第 k 个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。
数据范围
1≤M≤100000 所有操作保证合法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #include <iostream>
using namespace std;
const int N = 100010;
int head, e[N], l[N], r[N], idx;
void init() { r[0] = 1; l[1] = 0; idx = 2; }
void add(int k, int x) { e[idx] = x; r[idx] = r[k]; r[k] = idx; l[idx] = k; l[r[idx]] = idx; idx ++ ; }
void remove(int k) { r[l[k]] = r[k]; l[r[k]] = l[k]; }
int main() { int M; cin >> M; init(); char flag[2]; int k, x; while (M -- ) { cin >> flag[0]; if (flag[0] == 'L') { cin >> x; add(0, x); } else if (flag[0] == 'R') { cin >> x; add(l[1], x); } else if (flag[0] == 'D') { cin >> k; remove(k + 1); } else if (flag[0] == 'I') { cin >> flag[1]; if (flag[1] == 'R') { cin >> k >> x; add(k + 1, x); } else if (flag[1] == 'L') { cin >> k >> x; add(l[k + 1], x); } } } int ptr = r[0]; while (ptr != 1) { cout << e[ptr] << " "; ptr = r[ptr]; } return 0; }
|
栈与队列
【一般实现方式:C++ STL中容器】
这里介绍用数组模拟该数据结构的方式。
栈
先进后出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const int N = 100010;
int stk[N], tt;
stk[ ++ tt] = x;
tt -- ;
if (tt > 0) else
stk[tt];
|
队列
先进先出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const int N = 100010;
int q[N], hh, tt = -1;
q[ ++ tt] = x;
hh ++ ;
if (hh <= tt) else
q[hh];
|
单调栈
单调队列
KMP