ruye07

导航

双向链表(数组模拟)学习笔记

双向链表(数组模拟)学习笔记

学习日期: 2026-05-14
来源: 洛谷 P1160 队列安排
前置知识: 数组


问题场景

image


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

posted on 2026-05-16 17:07  ruye07  阅读(37)  评论(0)    收藏  举报