双向链表(数组模拟)学习笔记
双向链表(数组模拟)学习笔记
学习日期: 2026-05-14
来源: 洛谷 P1160 队列安排
前置知识: 数组
问题场景

N 个同学,依次插入到某个已有同学的左边或右边,之后可能删除某些同学。最后输出从左到右的排列。N ≤ 10⁵。
为什么不用普通数组
[2, 3, 4, 1]
要在 3 左边插入 5 → 需要把 4,1 整体右移一位 → O(N)
N=10⁵ 时每步 O(N) 就是 10¹⁰,必超时
核心思想
每个元素记住左边是谁、右边是谁。 插入删除只需要改相邻元素的指针,O(1)。
每个节点存两个值:
left[i] = i 左边同学的编号(前驱),没有则为 0
right[i] = i 右边同学的编号(后继),没有则为 0
插入操作图解
插入右侧 (p=1): 把 y 插入到 x 右边
原来: ... ←→ x ←→ r ←→ ...
┌─ y ─┐
... ←→ x │ r ←→ ...
│
四步(顺序很重要):
① y.left = x y 记住:我左边是 x
② y.right = x.right y 记住:我右边是 x 原来的右边
┌─ y ──┐
│ ↓
... ←→ x r ←→ ...
│ ↑
└─ y ──┘
(y 已经指向两边,但两边还没指向 y)
③ left[x.right] = y x 原来的右边的左边 = y(如果 x.right 存在)
④ x.right = y x 的右边 = y
结果: ... ←→ x ←→ y ←→ r ←→ ...
顺序细节: 必须先保存 x.right,因为步骤④会覆盖它。所以步骤②用到了 x.right,必须在步骤④之前。
插入左侧 (p=0): 把 y 插入到 x 左边
原来: ... ←→ l ←→ x ←→ ...
对称操作:
① y.right = x y 右边是 x
② y.left = x.left y 左边是 x 原来的左边
③ right[x.left] = y x 原来左边的右边 = y(如果 x.left 存在)
④ x.left = y x 的左边 = y
结果: ... ←→ l ←→ y ←→ x ←→ ...
P1160 样例模拟
输入:
4 ← N
1 0 ← 2 号插入到 1 号左边
2 1 ← 3 号插入到 2 号右边
1 0 ← 4 号插入到 1 号左边
2 ← M
3 ← 删掉 3
3 ← 删掉 3(已不在,忽略)
逐步模拟
初始: 1 号已在队列
left[1] = 0 right[1] = 0
链表: [1]
① 2 插入到 1 左边 (k=1, p=0)
left[2] = left[1] = 0
right[2] = 1
right[left[1]] = right[0] → 0 没有 right,跳过
left[1] = 2
链表: 2 ←→ 1
② 3 插入到 2 右边 (k=2, p=1)
left[3] = 2
right[3] = right[2] = 1
left[right[2]] = left[1] = 3
right[2] = 3
链表: 2 ←→ 3 ←→ 1
③ 4 插入到 1 左边 (k=1, p=0)
left[4] = left[1] = 3
right[4] = 1
right[left[1]] = right[3] = 4
left[1] = 4
链表: 2 ←→ 3 ←→ 4 ←→ 1
④ 删除 3
deleted[3] = True (不打乱链表,遍历时跳过)
实际链表: 2 ←→ 3 ←→ 4 ←→ 1
遍历显示: 2 (跳过) 4 1 → 输出 "2 4 1"
⑤ 删除 3 → 已标记,忽略
模板代码
N = int(input())
left = [0] * (N + 1)
right = [0] * (N + 1)
deleted = [False] * (N + 1)
# 1 号已经在队列
left[1] = 0
right[1] = 0
# 2~N 依次插入
for i in range(2, N + 1):
k, p = map(int, input().split())
if p == 0: # 插到 k 左边
left[i] = left[k]
right[i] = k
if left[k] != 0:
right[left[k]] = i
left[k] = i
else: # 插到 k 右边
left[i] = k
right[i] = right[k]
if right[k] != 0:
left[right[k]] = i
right[k] = i
M = int(input())
for _ in range(M):
x = int(input())
deleted[x] = True
# 找最左边的(头)
head = 0
for i in range(1, N + 1):
if left[i] == 0 and not deleted[i]:
head = i
break
# 遍历输出
cur = head
ans = []
while cur != 0:
if not deleted[cur]:
ans.append(str(cur))
cur = right[cur]
print(' '.join(ans))
关键要点
| 要点 | 说明 |
|---|---|
| 数组模拟 | left[N+1] right[N+1] 代替指针,下标=编号 |
| 四步顺序 | 先设新节点再改老节点,避免丢失信息 |
| 删除方式 | 懒删除:标记 deleted,遍历时跳过 |
| 找头 | 头就是 left[i] == 0 的那个(且没被删) |
| 复杂度 | 每次插入/删除 O(1),遍历 O(N) |
| 边界 | 头尾的 left/right = 0,操作前要判 0 |
浙公网安备 33010602011771号