块状链表

鸣谢:董dalaoAIR_H等人

简介

在c++入门中,最先学习的数据结构就是数组
等级高了一点之后,又接触了一种链表的高级数据结构(一般都是用数组实现吧)

数组
所有数据在内存中是紧凑储存的,优点是定位快:O(1),缺点是修改慢:O(n)
链表
可以说是恰好相反,通过指针将不同位置的元素链接起来,优缺点与数组正好相反:定位慢 O(n),修改快 O(1)

块状链表

进入正题—>
数组和链表可以说是两种比较极端的数据结构,要是我们能结合两者的优点就好了
于是就有前辈利用奇技淫巧发明了一种叫“块状链表”的数据结构

整体来看,块状链表是一个链表,而在链表的每个结点上,以数组的形式存储一组元素(如图)
这里写图片描述

基本操作

一 . 定位

在查找一个元素的时候,我们在块状链表中先定位元素所在的链表结点
然后在定位元素在数组中的位置
从链首开始扫描,每一个链表结点都记录了本结点合法数据的长度,最重会定位在某一个块及块内偏移

二 . 分裂

将一个结点分裂成两个
这里写图片描述

三 . 插入

首先定位插入位置,将所在结点分裂成两个结点,将数据放到一个结点的末尾
如果插入的是一大段数据,首先要将插入数据分割成多个block,把这些block链接起来,然后将ta们插入
这里写图片描述

四 . 删除

首先定位删除元素的位置,按照数组删除元素的删除该元素
如果删除的是一大段数据,首先要定位数据的首元素和尾元素所在的结点
分别将ta们所在的结点分裂成两个,最后删除首元素和尾元素之间的结点即可
这里写图片描述

五 . 及时合并小分块

这里写图片描述

复杂度

该算法的核心就是确定链表长度和每个结点的数组长度,并且保证这个长度
设块状链表中元素总个数是x,链表长度是n,每个结点中数据结点为m
当m和n同时最小时,此时各种操作的时间复杂度最低
在实际应用中,需维持块状链表的每个结点大小在 [sqrt(n)/2,2sqrt(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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值