块状链表 代码小记

来源:互联网 发布:轻而易举瓷砖设计软件 编辑:程序博客网 时间:2024/05/21 00:52

块状链表作为一个理论上简单易懂但实际操作上对我来说几乎不可行的算法,很有必要拿来总结一番。今天是时候做个了结了。
下面将简单介绍一下块状链表的概念及实现。

1.块状链表(Block List)(假翻译)
块状链表能干啥?这要从数组和链表的优缺点说起。数组是随机访问容器,链表是顺序访问容器。因此它们的基本操作的时间复杂度大概是这样的:

数组 链表 访问 O(1) O(n) 插入/删除 O(n) O(1)

这就产生了一个问题:如果我要同时进行大量的访问和插入呢?这时数组和链表的时间复杂度都会退化成 O(nm) 了。所以需要用一个数据结构来综合一下数组和链表的优缺点,它就是块状链表。

块状链表,就是把几个数组看做一个链表上的结点,然后用链表把它们链起来,这就组成了一个典型的块状链表。直觉告诉我们,当每一个数组的大小和数组的个数相等时,块状链表的平均时间复杂度是最稳定的。因此不难得到,当块状链表的数组大小和个数均约为 O(n) 时,块状链表的效率最高。此时,就能算出块状链表的时间复杂度了:

块状链表 访问 O(nn) 插入/删除 O(nn)

在 n = 100000 时,该算法能轻松通过。

2.实现(点都没有学过的出门右转找有图的教程
众所周知,链表是一个理论上最简单但实际操作相当复杂的数据结构,所以这里我们使用 STL 的链表。

#include <list>

注意:list 的 insert 操作是将新元素插入到已知迭代器的前面(front)!已知迭代器在插入后不改变!

然后定义我们的结构体:

struct Block{    INT s;    char v[size];    Block() : s() {}    Block(char* begin, char* end) : s(), v()    {        s = end - begin;        std::copy(begin, end, v);    }};

这里的 size 推荐开成 O(n) 的两倍左右。
编写两个构造函数可以让以后的操作更方便。

块状链表的结构体:(由于迷之错误,就不用带有模板的类了,这个自己改还是挺容易的)

class BlockList{    static const INT size = 5000;    struct Block    {        INT s;        char v[size];        Block() : s() {}        Block(char* begin, char* end) : s(), v()        {            s = end - begin;            std::copy(begin, end, v);        }    };    std::list<Block> blocks;    typedef std::list<Block>::iterator IT;    static IT Next(IT it) { IT ret = it; return ++ret; }    IT locate(INT& k);    void maintain();    void merge(IT l, IT r);    bool split(IT it, INT k);public:    void insert(char* begin, char* end, INT k);    void erase(INT k, INT s);    typedef void(*Func)(const char& v);    void wander(INT k, INT s, Func op);};

块状链表有 3 个内部操作(maintain, merge, split),3 个外部操作(insert, erase, wander)和 1 个辅助操作(locate)。下面让我来一一讲它们的实现。

①locate
我们先假设操作总是合法。设下标从 1 开始。
locate 的作用是找到下标为 k 的元素,返回其所在位置的迭代器以及在这块数组中,我们所需的元素在第几个位置。

    IT locate(INT& k)    {        IT it;        for (it = blocks.begin(); it != blocks.end(); it++)        {            if (k <= it->s)                return it;            k -= it->s;        }        return it;    }

若操作始终合法,则可以分成链表为空和链表不为空的情况。当链表不为空时,不难发现 locate 总是会返回有效的迭代器。当链表为空时,locate 将返回 blocks.end()。可见该操作在输入合法时正确性可以得到保证。
特别地,当 k = 0 时,该操作也将正常返回一个结果。
时间复杂度 O(n)

②maintain
maintain 的作用是从头到尾合并链表,使每个数组尽量达到饱和。

    void maintain()    {        IT it;        for (it = blocks.begin(); it != blocks.end(); it++)        {            IT next = Next(it);            while (next != blocks.end() && it->s + next->s <= size)            {                merge(it, next); //merge将自动删除next迭代器                next = Next(it);            }        }    }

时间复杂度 O(n)

③merge
merge 的作用是合并两个相邻块。理论上来说不相邻的也可以用 merge 合并,但不是我们所期望的结果。

    void merge(IT l, IT r)    {        std::copy(r->v, r->v + r->s, l->v + l->s);        l->s += r->s; //记住更新        blocks.erase(r);    }

时间复杂度 O(n)(复制数组)

④split
split 的作用是将一个块分成两个块,k 和 s - k。特别地,当 k = 0 时,仍然要在前面分出一个空块;当 k = s 时,不在后面分出一个空块。

    bool split(IT it, INT k)    {        if (k >= it->s) return false;        blocks.insert(Next(it), Block(it->v + k, it->v + it->s)); //注意insert的位置        it->s = k; //记住更新        return true;    }

时间复杂度 O(n)(复制数组)

⑤insert(在指定位置插入)
思路是:先定位,如果定位发现迭代器为空,则说明当前整个链表为空,直接插入就好了;否则将这个块 split,然后无论是否有新块出现,都让迭代器加一,因为我们的插入是插到当前迭代器的 front 位的。
插入时,若剩余内容大于等于块的大小,就开一个完整的块,否则就给剩下的内容(如果存在)单独开一个块来保存。最后,用 maintain 维护一下块状链表。

    void insert(char* begin, char* end, INT k)    {        IT it = locate(k);        if (it != blocks.end())        {            split(it, k);            it++;        }        while (end - begin >= size)        {            blocks.insert(it, Block(begin, begin + size));            begin += size; //记住维护,否则电脑爆炸        }        if (end - begin)        {            blocks.insert(it, Block(begin, end));        }        maintain();    }

时间复杂度 O(n+s)

⑥erase(在指定位置删除 s 个元素)
先定位。由于保证操作合法,所以直接 split,无论是否有新块出现,都让迭代器++,因为我们要删除的内容都在下一个迭代器。然后挨着删就可以啦!

    void erase(INT k, INT s)    {        IT it = locate(k);        split(it, k);        it++;        IT next = it;        while (s > 0)        {            next++;            if (it->s < s)            {                s -= it->s; //一定要先维护!                blocks.erase(it);            }            else            {                split(it, s);                s -= it->s; //一定要先维护!                blocks.erase(it);            }            it = next;        }        maintain();    }

前提是输入保证合法。
时间复杂度 O(n)

⑦wander(从第 k 个位置开始遍历 s 个)
这个很简单,定位后一个一个走就可以了。

    void wander(INT k, INT s, Func op)    {        IT it = locate(k);        k--; //注意差一错误!        while (s--)        {            op(it->v[k++]);            if (k >= it->s)            {                it++;                k = 0;            }        }    }

op 可以使用 Lambda 函数。
时间复杂度 O(n+s)

3.完整代码

class BlockList{    static const INT size = 5000;    struct Block    {        INT s;        char v[size];        Block() : s() {}        Block(char* begin, char* end) : s(), v()        {            s = end - begin;            std::copy(begin, end, v);        }    };    std::list<Block> blocks;    typedef std::list<Block>::iterator IT;    static IT Next(IT it) { IT ret = it; return ++ret; }    IT locate(INT& k)    {        IT it;        for (it = blocks.begin(); it != blocks.end(); it++)        {            if (k <= it->s)                return it;            k -= it->s;        }        return it;    }    void maintain()    {        IT it;        for (it = blocks.begin(); it != blocks.end(); it++)        {            IT next = Next(it);            while (next != blocks.end() && it->s + next->s <= size)            {                merge(it, next);                next = Next(it);            }        }    }    void merge(IT l, IT r)    {        std::copy(r->v, r->v + r->s, l->v + l->s);        l->s += r->s;        blocks.erase(r);    }    bool split(IT it, INT k)    {        if (k >= it->s) return false;        blocks.insert(Next(it), Block(it->v + k, it->v + it->s));        it->s = k;        return true;    }public:    void insert(char* begin, char* end, INT k)    {        IT it = locate(k);        if (it != blocks.end())        {            split(it, k);            it++;        }        while (end - begin >= size)        {            blocks.insert(it, Block(begin, begin + size));            begin += size;        }        if (end - begin)        {            blocks.insert(it, Block(begin, end));        }        maintain();    }    void erase(INT k, INT s)    {        IT it = locate(k);        split(it, k);        it++;        IT next = it;        while (s > 0)        {            next++;            if (it->s < s)            {                s -= it->s;                blocks.erase(it);            }            else            {                split(it, s);                s -= it->s;                blocks.erase(it);            }            it = next;        }        maintain();    }    typedef void(*Func)(const char& v);    void wander(INT k, INT s, Func op)    {        IT it = locate(k);        k--;        while (s--)        {            op(it->v[k++]);            if (k >= it->s)            {                it++;                k = 0;            }        }    }};
原创粉丝点击