块状链表(unrolled Linked List)
块状链表(Unrolled Linked List)是一种介于数组和链表之间的数据结构。
它把多个元素“打包”在一个节点里,每个节点是一个小数组,然后节点之间再用链表连接。
简单理解:
👉 普通链表 = 一个节点存一个元素
👉 块状链表 = 一个节点存一组元素
一、结构示意图

每个节点包含:
- 一个数组(容量 B)
- 当前已存元素个数
- 指向下一个节点的指针
例如(块大小 B=4):[1 2 3] -> [4 5] -> [6 7 8 9]
二、为什么要用块状链表?
普通链表的问题:访问第 k 个元素是 O(n),缓存不友好(频繁跳指针)
数组的问题:插入删除 O(n)
块状链表的目标:
✅ 降低插入删除的移动成本
✅ 提高缓存局部性
✅ 在 O(√n) 时间内支持随机访问
三、时间复杂度(设块大小 B≈√n)
| 操作 | 时间复杂度 |
|---|---|
| 访问第 k 个元素 | O(√n) |
| 插入 | O(√n) |
| 删除 | O(√n) |
| 遍历 | O(n) |
为什么是 √n?
如果:总元素 n,每块 √n 个,则块数 ≈ √n
查找:先定位块(√n 次),再在块内找(√n 次)总复杂度 O(√n)
四、块状链表核心思想
插入时如果某块满了:分裂成两个块(类似 B 树节点分裂)
删除时:如果某块太空:可以和相邻块合并
五、典型应用
块状链表常用于:
- 维护可变长序列
- 支持区间操作
- 文本编辑器(rope 结构类似)
- OJ 题目中的“暴力分块”
- 维护动态数组
在竞赛中,它是“分块思想”的一种实现方式。
六、简单 C++ 实现示例
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | /**************************************************************** * 代码作者: Alex Li * 创建时间: 2026-03-03 16:49:46 * 最后修改: 2026-03-03 18:05:28 * 文件描述: 块状链表(unrolled linked list)功能: 1. 插入元素 2. 删除元素 3. 查询第 k 个元素 4. 自动分裂块 5. 自动简单合并块 时间复杂度: 单次操作 O(√n) 设计思想: - 每个节点存储一个小数组 - 节点之间用链表连接 - 当块过大 -> 分裂 - 当块过小 -> 尝试合并 ****************************************************************/ #include <bits/stdc++.h> using namespace std; int BLOCK_SIZE; // 不再是常量,根据 n 决定 struct Node { vector<int> data; Node* next; }; Node* head = nullptr; // 创建新节点 Node* newNode() { Node* node = new Node; node->next = nullptr; return node; } // 分裂 void split(Node* cur) { Node* newnode = newNode(); // 把 BLOCK_SIZE 之后的元素移到新块 newnode->data.assign(cur->data.begin() + BLOCK_SIZE, cur->data.end()); cur->data.erase(cur->data.begin() + BLOCK_SIZE, cur->data.end()); newnode->next = cur->next; cur->next = newnode; } // 合并 void merge(Node* cur) { if (!cur || !cur->next) return; if (cur->data.size() + cur->next->data.size() <= BLOCK_SIZE) { Node* nxt = cur->next; cur->data.insert(cur->data.end(), nxt->data.begin(), nxt->data.end()); cur->next = nxt->next; delete nxt; } } // 插入(1-index) void insert(int pos, int x) { if (!head) head = newNode(); Node* cur = head; int cnt = 0; while (cur) { if (cnt + cur->data.size() >= pos) { cur->data.insert(cur->data.begin() + (pos - cnt - 1), x); if (cur->data.size() > BLOCK_SIZE) split(cur); return; } cnt += cur->data.size(); if (!cur->next) break; cur = cur->next; } // 插入到末尾 cur->data.push_back(x); if (cur->data.size() > BLOCK_SIZE) split(cur); } // 删除 void erase_pos(int pos) { Node* cur = head; Node* prev = nullptr; int cnt = 0; while (cur) { if (cnt + cur->data.size() >= pos) { cur->data.erase(cur->data.begin() + (pos - cnt - 1)); if (cur->data.size() < BLOCK_SIZE / 2) merge(cur); if (cur->data.empty()) { if (prev) prev->next = cur->next; else head = cur->next; delete cur; } return; } cnt += cur->data.size(); prev = cur; cur = cur->next; } } // 查询 int query(int pos) { Node* cur = head; int cnt = 0; while (cur) { if (cnt + cur->data.size() >= pos) return cur->data[pos - cnt - 1]; cnt += cur->data.size(); cur = cur->next; } return -1; } // 打印块结构 void print_blocks() { Node* cur = head; int id = 1; while (cur) { cout << "Block " << id++ << " (size=" << cur->data.size() << "): "; for (int x : cur->data) cout << x << " "; cout << endl; cur = cur->next; } cout << "----------------------\n"; } int main() { int n; cout << "输入元素个数: "; cin >> n; // 根据 n 自动设块大小 int s = (int)sqrt(n); if (s * s == n) BLOCK_SIZE = s; else BLOCK_SIZE = s + 1; cout << "BLOCK_SIZE = " << BLOCK_SIZE << endl; cout << "输入元素:\n"; for (int i = 1; i <= n; i++) { int x; cin >> x; insert(i, x); // 插入到末尾 } cout << "\n初始分块结果:\n"; print_blocks(); //erase_pos(13); //print_blocks(); return 0; } |
七、数组 vs 链表 vs 块状链表
| 特性 | 数组 | 链表 | 块状链表 |
|---|---|---|---|
| 随机访问 | O(1) | O(n) | O(√n) |
| 插入删除 | O(n) | O(1) | O(√n) |
| 缓存友好 | ⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐ |
| 实现难度 | 简单 | 简单 | 中等 |
| 适合场景 | 多查询 | 多插删 | 动态序列 |
NOI2003 (P4008洛谷)文本编辑器
