本篇内容只讲采用数组模拟来链表!!!
一、前置知识
1. 链表本质
链表由一个个节点串联而成,每个节点包含两部分:
- 数据域:存储当前节点的值
- 指针域:存储下一个 / 上一个节点的位置
节点之间不要求连续内存,靠指针域关联,这是链表和数组最大的区别。
3. 数组模拟核心思想
用两个数组 分别对应节点的数据域和指针域,用下标表示节点地址:
val[]:数据域,val[i]表示下标为i的节点存储的值next[]/prev[]:指针域,next[i]表示下标为i的节点的下一个节点下标- 用变量记录头节点、尾节点、当前可用下标,管理整个链表
二、单链表
1. 单链表的结构
单链表每个节点只有一个指针域,只能向后遍历,
结构如下:头节点 → 节点1 → 节点2 → ... → 尾节点 → 空
2.核心函数模版
初始化
链表初始状态:无头节点,无可用节点
cpp
//初始化
void init(){
head = -1;//指向空集
idx = 0;//当前可用的节点的编号
}
在链表头部插入一个元素
步骤
- 给新节点分配下标
idx - 新节点的值 = 要插入的数
- 新节点的 next 指向原头节点
- 头节点更新为新节点下标
- 可用下标 + 1

cpp
//在头结点插入一个元素
void add_to_head(int x){
//存储值
val[idx] = x;
next[idx] = head; //把当前这个节点指向原来头结点的指向
head = idx;//指向这个节点
idx++;//编号++
}
删除某个节点后面的一个元素
步骤:直接让第 k 个节点的 next,跳过它的下一个节点,指向下下个节点即可

cpp
//删除第k个元素之后的一个元素
void delete_k(int k){
next[k] = next[next[k]];
}
在某个节点后面插入一个元素
步骤
- 分配新节点下标
idx - 新节点 next 指向「第 k 个节点」的 next 节点
- 「第 k 个节点」的 next 指向新节点
- 可用下标 + 1
l
cpp
//在某个节点之后插入一个元素
void add_to_k(int k,int x){
val[idx] = x;
next[idx] = next[k];
next[k] = idx;
idx++;
}
3.例题
题目描述(来源于AcWing)


思路
- 数组模拟链表 = val 存值 + ne 存下一节点
- 第 k 个插入节点 = 下标 k-1
- 删除头节点 = head = ne [head]
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int head;
int val[N];//存储值
int ne[N];//存储当前节点的下一个节点的位置
int idx;//表示当前用的结点
//初始化
void init(){
head = -1;//指向空集
idx = 0;//当前可用的节点的编号
}
//在头结点插入一个元素
void add_to_head(int x){
//存储值
val[idx] = x;
ne[idx] = head; //把当前这个节点指向原来头结点的指向
head = idx;//指向这个节点
idx++;//编号++
}
//删除第k个元素之后的一个元素
void delete_k(int k){
ne[k] = ne[ne[k]];
}
//在某个节点之后插入一个元素
void add_to_k(int k,int x){
val[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
int main(){
int m;
cin>>m;
//初始化
init();
while(m--){
char c;
cin>>c;
if(c == 'H'){
int x;
cin>>x;
//头插法
add_to_head(x);
}else if(c == 'D'){
int k;
cin>>k;
if(k == 0){
//k为0时删除头结点
head = ne[head];
}else{
delete_k(k - 1);
}
}else if(c == 'I'){
int k,x;
cin>>k>>x;
add_to_k(k - 1,x);
}
}
//输出
for(int i = head;i != -1; i = ne[i]){
cout<< val[i] << " ";
}
return 0;
}
三、双链表
1. 双链表的结构
双链表每个节点有两个指针域 :前驱指针+后继指针,可以向前 / 向后遍历,解决了单链表无法逆向查找的问题。
结构:头节点 ←→ 节点1 ←→ 节点2 ←→ ... ←→ 尾节点
2.核心函数模版
初始化
cpp
//初始化
void init(){
l[1] = 0;
r[0] = 1;
idx = 2;
}
在某个节点的右边插入一个元素
步骤
- 分配新节点
idx - 新节点左指针指向 k,右指针指向 k 的原右节点
- k 的原右节点的左指针指向新节点
- k 的右指针指向新节点
- 可用下标 + 1

cpp
//在下标k的右边插入一个元素
void add(int k,int x){
val[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
idx++;
}
删除某个节点
步骤:让 k 的前驱节点直接指向 k 的后继节点,跳过 k 即可

cpp
//删除下标k的后一个元素
void delete(int k){
r[l[k]] = r[k];
l[r[k]] = l[k];
}
3.例题
题目描述


思路
- 用三个数组
l、r、val模拟双链表,0、1 为虚拟头尾节点 idx=2从 2 开始分配真实数据节点add(k,x):在 k 右边插入节点delet(k):删除 k 节点- 题目第 k 个插入数 = 数组下标 k+1(你代码的核心对应规则)
- 按规则完成 5 种操作,最后从左到右遍历输出
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int l[N],r[N],val[N];
int idx;
//初始化
void init(){
l[1] = 0;
r[0] = 1;
idx = 2;
}
//在下标k的右边插入一个元素
void add(int k,int x){
val[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
idx++;
}
//删除下标k的后一个元素
void delet(int k){
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main(){
int m;
cin>>m;
init();
while(m--){
string c;
cin>>c;
if(c == "L"){
int x;
cin>>x;
add(0,x);
}else if(c == "R"){
int x;
cin>>x;
add(l[1],x);
}else if(c == "D"){
int k;
cin>>k;
delet(k + 1);
}else if(c == "IL"){
int x,k;
cin>>k>>x;
add(l[k + 1],x);
}else if(c == "IR"){
int k,x;
cin>>k>>x;
add(k + 1,x);
}
}
//输出
for(int i = r[0]; i != 1; i = r[i]){
cout<< val[i] <<" ";
}
}
四、对比总结
| 特性 | 单链表 | 双链表 |
|---|---|---|
| 指针域数量 | 1 个(后继指针) | 2 个(前驱 + 后继指针) |
| 遍历方向 | 仅向后遍历 | 双向遍历 |
| 插入 / 删除 | 仅能操作后继节点 | 可直接删除当前节点 |
| 内存占用 | 更小 | 更大(多一个指针数组) |
| 适用场景 | 邻接表、栈、简单链表 | 双向遍历、LRU 缓存等 |
| 实现难度 | 简单 | 中等(哨兵节点简化) |