块状链表(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 树节点分裂)

删除时:如果某块太空:可以和相邻块合并

五、典型应用

块状链表常用于:

  1. 维护可变长序列
  2. 支持区间操作
  3. 文本编辑器(rope 结构类似)
  4. OJ 题目中的“暴力分块”
  5. 维护动态数组

在竞赛中,它是“分块思想”的一种实现方式。

六、简单 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洛谷)文本编辑器