简介
在c++入门中,最先学习的数据结构就是数组
等级高了一点之后,又接触了一种链表的高级数据结构(一般都是用数组实现吧)
数组:
所有数据在内存中是紧凑储存的,优点是定位快:O(1),缺点是修改慢:O(n)
链表:
可以说是恰好相反,通过指针将不同位置的元素链接起来,优缺点与数组正好相反:定位慢 O(n),修改快 O(1)
块状链表
进入正题—>
数组和链表可以说是两种比较极端的数据结构,要是我们能结合两者的优点就好了
于是就有前辈利用奇技淫巧发明了一种叫“块状链表”的数据结构
整体来看,块状链表是一个链表,而在链表的每个结点上,以数组的形式存储一组元素(如图)
基本操作
一 . 定位
在查找一个元素的时候,我们在块状链表中先定位元素所在的链表结点
然后在定位元素在数组中的位置
从链首开始扫描,每一个链表结点都记录了本结点合法数据的长度,最重会定位在某一个块及块内偏移
二 . 分裂
将一个结点分裂成两个
三 . 插入
首先定位插入位置,将所在结点分裂成两个结点,将数据放到一个结点的末尾
如果插入的是一大段数据,首先要将插入数据分割成多个block,把这些block链接起来,然后将ta们插入
四 . 删除
首先定位删除元素的位置,按照数组删除元素的删除该元素
如果删除的是一大段数据,首先要定位数据的首元素和尾元素所在的结点
分别将ta们所在的结点分裂成两个,最后删除首元素和尾元素之间的结点即可
五 . 及时合并小分块
复杂度
该算法的核心就是确定链表长度和每个结点的数组长度,并且保证这个长度
设块状链表中元素总个数是x,链表长度是n,每个结点中数据结点为m
当m和n同时最小时,此时各种操作的时间复杂度最低
在实际应用中,需维持块状链表的每个结点大小在
[sqrt(n)/2,2∗sqrt(n)]
否则时间会退化的很厉害
因此我们在适当的时候,需要对结点进行合并与分裂
const int N=1<<25;
const int blocksize=20000;
const int blocknum=N/blocksize*3;
int n,cur;
char s[N+10];
queue<int> q; //内存池
struct node{
char data[blocksize];
int len,nxt; //记录块中的元素个数 下一个块的编号(模拟链表)
};
node a[blocknum+100];
int newnode() //申请新结点
{
int t=q.front(); q.pop();
return t;
}
void delnode(int t) //删除结点
{
q.push(t);
}
void find(int &pos,int &now) //从第一个块开始搜索,搜索位置pos所属的块的编号
{
for (now=0;a[now].nxt!=-1&&pos>a[now].len;now=a[now].nxt) pos-=a[now].len;
}
void fillnode(int pos,int n,char data[],int nxt)
{
a[pos].nxt=nxt; a[pos].len=n;
memcpy(a[pos].data,data,n); //将data中前n个元素复制到块pos中
}
void divide(int pos,int p) //分裂
{
if (a[pos].len==p) return;
int t=newnode();
fillnode(t,a[pos].len-p,a[pos].data+p,a[pos].nxt);
a[pos].nxt=t;
a[pos].len=p;
}
void maintain(int pos) //当前块与后一个块合并
{
int t;
for (;pos!=-1;pos=a[pos].nxt)
for (t=a[pos].nxt;t!=-1&&a[pos].len+a[t].len<blocksize;t=a[t].nxt)
{
memcpy(a[pos].data+a[pos].len,a[t].data,a[t].len);
a[pos].len+=a[t].len;
a[pos].nxt=a[t].nxt;
delnode(t);
}
}
void insert(int pos,int n)
{
int now,i,t; //now记录当前位置所在的块的编号
find(pos,now);
divide(now,pos);
for (i=0;i+blocksize<=n;i+=blocksize)
{
t=newnode();
fillnode(t,blocksize,s+i,a[now].nxt);
a[now].nxt=t;
now=t;
}
if (i<n) //剩下的元素不足以填满一个块,需要特殊处理一下
{
t=newnode();
fillnode(t,n-i,s+i,a[now].nxt);
a[now].nxt=t;
}
maintain(now);
}
void del(int pos,int n)
{
int i,now,t;
find(pos,now);
divide(now,pos);
//删除的时候先处于第一个位置所在的块,通过拆块的方式使当前块中要删除的元素单独分到一个块中
for (i=a[now].nxt;i!=-1&&n>a[i].len;i=a[i].nxt)
n-=a[i].len;
divide(i,n);
i=a[i].nxt;
for (t=a[now].nxt;t!=i;t=a[now].nxt)
a[now].nxt=a[t].nxt,delnode(t);
maintain(now);
}
void get(int pos,int n)
{
int i,now,t;
find(pos,now);
i=min(n,a[now].len-pos);
memcpy(s,a[now].data+pos,i);
for (t=a[now].nxt;t!=-1&&i+a[t].len<=n;t=a[t].nxt)
{
memcpy(s+i,a[t].data,a[t].len);
i+=a[t].len;
}
if (i<n&&t!=-1) memcpy(s+i,a[t].data,n-i);
s[n]=0;
}
void init() //初始化内存池
{
for (int i=1;i<=blocknum;i++) q.push(i);
a[0].len=0; a[0].nxt=-1;
}