块状链表详解:原理、实现与应用
创作时间:
作者:
@小白创作中心
块状链表详解:原理、实现与应用
引用
1
来源
1.
https://oi-wiki.org/ds/block-list/
块状链表(Block List)是一种高效的数据结构,结合了链表和数组的优点,特别适合处理大规模数据的插入、删除和查询操作。本文将详细介绍块状链表的基本概念、实现方法以及在算法竞赛中的应用。
块状链表的基本思想是将一个长数组分割成多个块,每个块用一个链表节点表示,节点内部是一个固定大小的数组。这种结构可以有效平衡随机访问和插入/删除操作的效率。
块状链表的实现
块状链表应该至少支持:分裂、插入、查找。 什么是分裂?分裂就是分裂一个
node
,变成两个小的
node
,以保证每个
node
的大小都接近(否则可能退化成普通数组)。当一个
node
的大小超过时执行分裂操作。
分裂操作怎么做呢?先新建一个节点,再把被分裂的节点的后个值
copy
到新节点,然后把被分裂的节点的后个值删掉(
size--
),最后把新节点插入到被分裂节点的后面即可。
块状链表的所有操作的复杂度都是的。
还有一个要说的。 随着元素的插入(或删除),会变,也会变。这样块的大小就会变化,我们难道还要每次维护块的大小?
其实不然,把设置为一个定值即可。比如题目给的范围是,那么就设置为大小为的常量,不用更改它。
1 list<vector
结构体定义
struct node {
node* nxt;
int size;
char d[(sqn << 1) + 5];
node() {
size = 0, nxt = NULL;
memset(d, 0, sizeof(d));
}
void pb(char c) {
d[size++] = c;
}
};
基本操作
- 分裂操作:当一个节点的大小超过预设阈值时,需要将其分裂成两个节点,以保持每个节点的大小接近。
- 插入操作:在指定位置插入元素,可能需要更新多个节点。
- 查找操作:通过遍历链表和数组索引实现元素查找。
STL中的rope容器
STL中的rope容器也起到块状链表的作用,它采用可持久化平衡树实现,可完成随机访问和插入、删除元素的操作。虽然rope并不是真正的用块状链表来实现,但其功能与块状链表类似,时间复杂度相当于可持久化平衡树的复杂度(即O(log n))。
基本操作
操作 | 作用 |
|---|---|
rope<int> a | 初始化rope(与vector等容器很相似) |
a.push_back(x) | 在a的末尾添加元素x |
a.insert(pos, x) | 在a的pos个位置添加元素x |
a.erase(pos, x) | 在a的pos个位置删除x个元素 |
a.at(x)或a[x] | 访问a的第x个元素 |
a.length()或a.size() | 获取a的大小 |
例题:POJ2887 Big String
这是一道经典的块状链表应用题目,主要考察块状链表的插入和查询操作。以下是完整的代码实现:
#include <cctype>
#include <cstring>
#include <iostream>
using namespace std;
const int sqn = 1e3;
struct node {
node* nxt;
int size;
char d[(sqn << 1) + 5];
node() {
size = 0, nxt = NULL;
}
void pb(char c) {
d[size++] = c;
}
};
char inits[(int)1e6 + 5];
int llen, q;
void readch(char& ch) {
do cin >> ch;
while (!isalpha(ch));
}
void check(node* p) {
if (p->size >= (sqn << 1)) {
node* q = new node;
for (int i = sqn; i < p->size; i++)
q->pb(p->d[i]);
p->size = sqn, q->nxt = p->nxt, p->nxt = q;
}
}
void insert(char c, int pos) {
node* p = head;
int tot, cnt;
if (pos > llen++) {
while (p->nxt != NULL)
p = p->nxt;
p->pb(c), check(p);
return;
}
for (tot = head->size; p != NULL && tot < pos; p = p->nxt, tot += p->size);
tot -= p->size, cnt = pos - tot - 1;
for (int i = p->size - 1; i >= cnt; i--)
p->d[i + 1] = p->d[i];
p->d[cnt] = c, p->size++;
check(p);
}
char query(int pos) {
node* p;
int tot;
for (p = head, tot = head->size; p != NULL && tot < pos; p = p->nxt, tot += p->size);
tot -= p->size;
return p->d[pos - tot - 1];
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> inits >> q;
llen = strlen(inits);
node* p = new node;
head = p;
for (int i = 0; i < llen; i++) {
if (i % sqn == 0 && i)
p->nxt = new node, p = p->nxt;
p->pb(inits[i]);
}
char a;
int k;
while (q--) {
readch(a);
if (a == 'Q')
cin >> k, cout << query(k) << '\n';
else
readch(a), cin >> k, insert(a, k);
}
return 0;
}
通过这个例子,我们可以看到块状链表在处理大规模字符串操作时的高效性。它结合了链表的插入优势和数组的随机访问优势,是一种非常实用的数据结构。
热门推荐
成语“弹冠相庆”出自哪里?其中又有哪些历史典故呢?
金匮肾气丸可以长期服用吗?医生的专业解答
逍遥丸最常用的8个神仙搭配中成药,一文总结逍遥丸,这一源自古代中
水位监测设备的工作原理有哪些
如何在秋冬优雅驾驭红色:穿搭比例与细节搭配指南
芝加哥大学董广彬课题组:铱催化羧酸氧化还原中性转移脱氢反应新突破
什么是键值对?它在数据结构中扮演着怎样的角色?
“酒精依赖”需不需要治疗?看了这篇文章你就明白了
10道农家下饭菜,做法简单味道香韧,拌饭一绝
chillguy是什么梗?
为什么员工网络安全培训很重要
【PLC网络协议揭秘】:C#与S7-200 SMART握手全过程大公开
加拿大滑铁卢高斯数学竞赛GAUSS简介、五大要点 vs 参赛须知
打呼噜声音很大要看什么科?打呼噜声音很大怎么改善?
肌少症不只是老年问题:年轻人如何预防与改善肌肉流失?
肌肉萎缩康复训练方法
半导体后端工艺:不同晶圆级封装的工艺流程详解
应对指南!牙周炎牙龈萎缩牙齿松动怎么办?了解根源进行预防护理
痘坑痘印的形成原因!以及有效的治疗方法~
官宣换帅后,荣耀IPO进程或将提速
足协裁委会第二期裁判评议 中超争议判罚三错二
AI时代的加速器:NPU与GPU的性能对决与应用解析!
怀孕期间需要查哪些胎儿体格检查项目?
出血倾向常见的原因有哪三类呢
单片机C语言编程中SFR的使用详解
火星上有什么?揭秘太阳系最神秘的红色星球!
所有的药物都不能用饮料送服?
涿鹿之战到底发生在哪里?成都平原的可能性
软骨质损伤的恢复方法与日常保健指南
加强舆情监控的关键措施:实现全面覆盖与快速响应