【块状链表】AutSky_JadeK的块状链表模板+总结(STL版)
Part 1、块状链表。
定位 | 插入 | 删除 | |
数组 | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) |
对于线性表的以上常见操作来说,数组和链表都无法有效地解决。但是,若我们将链表的每个节点存成一个数组,使得链表里每个节点的数据拼接起来就是原先的线性表中的内容(即块状链表),并且数组的大小合适的话,以上的操作都能比较好地解决了。根据均值不等式,若每个块的大小定为sqrt(n)左右最优,此时块数也是sqrt(n)左右,易证。以下是块状链表的基础操作的思想、复杂度和代码。
一、声明。C++ STL中提供了list和vector两种数据结构,于是,若我们将它们嵌套起来,便是一个很方便的块状链表,免去指针之类的操作。这里的每个块仅仅存储了数据,没有存储额外的域,实际情况要予以添加。
next函数用于返回list的迭代器x的下一个迭代器。
#include<cstdio>
#include<vector>
#include<list>
using namespace std;
int sz;//块大小,一般为sqrt(n)
struct BLOCK{vector<int>data;BLOCK(){}};
list<BLOCK>List;
typedef list<BLOCK>::iterator L_ITER;
typedef vector<int>::iterator V_ITER;
inline L_ITER next(L_ITER x){x++; return x;}//返回x的下一个块
*下文中的curB指当前块,nextB指下一个块,newB指新块
二、定位。需要沿着链表顺序查找,假设查询线性表中第p(下标从1开始)个数,那么需要找到块Blockt,使得t*sz>=p,且(t-1)*sz<p。所以,查询的复杂度与块数同阶,即O(sqrt(n))。
inline L_ITER Find_Pos(const int &p)//返回整个块链中,下标p所在的块,下标默认从1开始
{
int cnt=;
for(L_ITER it=List.begin();it!=List.end();it++)
{
cnt+=(*it).data.size();
if(cnt>=p) return it;
}
}
三、维护块状链表的形态。我们一般保证块的大小在[sqrt(n)/2,sqrt(n)*2]之内,块链不会退化,因此,若相邻两块的大小之和不超过sqrt(n),则将它们合并。我们在这里采取将相邻两块的大小加起来>sqrt(n),且每块大小不超过sqrt(n)的写法。之所以采取这种写法,是因为我们不需要考虑将过大块分裂的情况。
(1)合并:目的是在相邻两块大小之和不超过sqrt(n)时进行合并。复杂度显然是O(sqrt(n))。
void Merge(L_ITER a,L_ITER b)//将b合并给a
{
(*a).data.insert((*a).data.end(),(*b).data.begin(),(*b).data.end());
List.erase(b);
}
(2)扫一遍块链维护形态,这在很多操作之后都要进行。由于块链插入和删除的特点,使得每次要维护的块数相对较少,所以这个复杂度仍然是O(sqrt(n))。
void MaintainList()//维护块链的形态,保证每块的元素数恰当
{
L_ITER curB=List.begin();//将指针置于链表表头
while(curB!=List.end())
{
L_ITER nextB=next(curB);
while(nextB!=List.end()&&(*curB).data.size()+(*nextB).data.size()<=sz)
{
Merge(curB,nextB);
nextB=next(curB);
}
curB++;
}
}
四、分裂。主要用于插入和删除时对最左右两端的块的操作。复杂度显然是O(sqrt(n))。
void Split(L_ITER curB,int p)//在curB的p前分裂该块
{
if(p==(*curB).data.size()) return;//分裂的位置在末尾,不需要分裂
L_ITER newB=List.insert(next(curB),BLOCK());//在curB的后面插入一个新的块
(*newB).data.assign((*curB).data.begin()+p,(*curB).data.end());//将原来块的后半部分数据复制给新块
(*curB).data.erase((*curB).data.begin()+p,(*curB).data.end());//将原来块中的后半部分元素删除
}
五、插入。若是插入一段元素,这里的复杂度应该是O(length)(插入部分长度),我们这里不予以考虑(有缩点等办法解决)。因此时间消耗主要来自分裂。
具体做法是:先定位,再将原块分裂,然后我们将待插入的数据组织成最紧凑的形式,即前面若干个大小为sqrt(n)的块、最后添上一个余块的形式,插入到原链表中。
void Insert(const int &p,const int &x,const int &v)//在p处插入x个数,待插入的权值均为v
{
L_ITER curB=Find_Pos(p);
Split(curB,p);
int cnt=;
while(cnt+sz<=x)
{
L_ITER newB=List.insert(next(curB),BLOCK());
(*newB).data.assign(sz,v);//设置新块的数据
curB=newB;
cnt+=sz;
}
if(x-cnt!=)
{
L_ITER newB=List.insert(next(curB),BLOCK());
(*newB).data.assign(sz,v);//设置新块的数据
}
MaintainList();
}
六、删除。先定位,若整块删除,则为O(1),若块被部分删除,则先分裂再删除。因此复杂度为O(sqrt(n))。
void Erase(const int &p,int x)//删除块链中从p位置开始的x个数
{
L_ITER curB=Find_Pos(p);
Split(curB,p); curB++;
L_ITER nextB=curB;
while(nextB!=List.end()&&x>(*nextB).data.size())
{
x-=(*nextB).data.size();
nextB++;
}
Split(nextB,x);
List.erase(curB,next(nextB));//将[curB,nextB]全部删除
MaintainList();
}
*至此,我们定义完了块状链表的基本操作。
Part 2、可持久化块状链表。
---------------------------------------------------------------未完待续----------------------------------------------------------------------------
参考资料: ①苏煜 《对块状链表的一点研究》;②《青少年信息学奥林匹克竞赛实战辅导丛书·高级数据结构》;③陈立杰 《可持久化数据结构研究》。
【块状链表】AutSky_JadeK的块状链表模板+总结(STL版)的更多相关文章
- 堆模板(STL版)
题目描述 如题,初始小根堆为空,我们需要支持以下3种操作: 操作1: 1 x 表示将x插入到堆中 操作2: 2 输出该小根堆内的最小数 操作3: 3 删除该小根堆内的最小数 输入输出格式 输入格式: ...
- C++的标准模板库STL中实现的数据结构之链表std::list的分析与使用
摘要 本文主要借助对C++的标准模板库STL中实现的数据结构的学习和使用来加深对数据结构的理解,即联系数据结构的理论分析和详细的应用实现(STL),本文是系列总结的第二篇.主要针对线性表中的链表 ST ...
- JAVA 链表操作:单链表和双链表
主要讲述几点: 一.链表的简介 二.链表实现原理和必要性 三.单链表示例 四.双链表示例 一.链表的简介 链表是一种比较常用的数据结构,链表虽然保存比较复杂,但是在查询时候比较便捷,在多种计算机语言都 ...
- 【编程题目】输入一个单向链表,输出该链表中倒数第 k 个结点
第 13 题(链表):题目:输入一个单向链表,输出该链表中倒数第 k 个结点.链表的倒数第 0 个结点为链表的尾指针.链表结点定义如下: struct ListNode {int m_nKey;Lis ...
- C语言 链表的使用(链表的增删查改,链表逆转,链表排序)
//链表的使用 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include< ...
- 输入一个单向链表,输出该链表中倒数第K个结点
输入一个单向链表,输出该链表中倒数第K个结点,具体实现如下: #include <iostream> using namespace std; struct LinkNode { publ ...
- 算法:输入一个链表,输出该链表中倒数第k个结点。
算法:输入一个链表,输出该链表中倒数第k个结点.<剑指offer> 思路加到注释里面了: 1:两个if判断是否返回值为空,首个为空,没有第k个值: 2:for循环找到倒数第k个值,返回为a ...
- [PHP] 算法-合并两个有序链表为一个有序链表的PHP实现
合并两个有序的链表为一个有序的链表: 类似归并排序中合并两个数组的部分 1.遍历链表1和链表2,比较链表1和2中的元素大小 2.如果链表1结点大于链表2的结点,该结点放入第三方链表 3.链表1往下走一 ...
- 数据结构与算法之PHP实现链表类(单链表/双链表/循环链表)
链表是由一组节点组成的集合.每个节点都使用一个对象的引用指向它的后继.指向另一个节点的引用叫做链表. 链表分为单链表.双链表.循环链表. 一.单链表 插入:链表中插入一个节点的效率很高.向链表中插 ...
随机推荐
- 从零开始学习MXnet(四)计算图和粗细粒度以及自动求导
这篇其实跟使用MXnet的关系不大,但对于我们理解深度学习的框架设计还是很有帮助的. 首先还是对promgramming models的一个简单介绍,这个东西实际上是在编译里面经常出现的东西,我们在编 ...
- cloudera manager配置
cloudera manager的数据库配置文件位置: /etc/cloudera-scm-server/db.properties
- [HTML]去除li前面的小黑点,和ul、LI部分属性[转]
转摘自:http://blog.csdn.net/cqkxzyi/article/details/7606181 对于很多人用div来做网站时,总会用到,但在显示效果时前面总是会有一个小黑点,这个令很 ...
- [05]Git查看、删除、重命名远程分支和tag
Git查看.删除.重命名远程分支和tag 2015-06-15:加入姊妹篇: 2013-11-06:加入重命名远程分支的内容: 2013-01-09:加入删除远程tag的内容. 姊妹篇:使用Git.G ...
- AngularJs学习——实现列表内容项的增加删除
效果截图: 说明:引入bootstrap.min.css样式库和angular.min.js的静态资源库,实现列表内容的增加和删除操作. AngularJS代码: <script src=&qu ...
- 【BZOJ1146】【CTSC2008】网络管理 [整体二分]
网络管理 Time Limit: 50 Sec Memory Limit: 162 MB[Submit][Status][Discuss] Description M公司是一个非常庞大的跨国公司,在 ...
- 【BZOJ】1691: [Usaco2007 Dec]挑剔的美食家
[算法]扫描线+平衡树(set) [题解]很明显的二维偏序数点,排序后扫描线,现加点后查询答案. 则问题转化为一维偏序,显然贪心找第一个比当前大的最优,所以用平衡树维护. 记得开multiset!!! ...
- 将setter方法与itemClick: 进行类比
https://www.evernote.com/shard/s227/sh/a0c3afa3-8792-4756-8594-d2387a7f57ad/b561ff665af9ad401c8e ...
- 24式太极拳:3D动画演示(图文)
http://blog.sina.com.cn/s/blog_4be33b740102e9ae.html 24式太极拳:3D动画演示(图文) (2013-03-10 18:45:55) 转载▼ 标签: ...
- UI变化之动画效果
很多时候我们在需要动态的改变某一个场景下的显示. 最常见的一个场景就是view的最大化. 我们直接设置view的frame可以实现最大化,但是这样的最大化是突变的没有动画效果. 苹果可以将这种突变“放 ...