skipList

如何在大量元素中去查找某个元素。例如在下面的“大量元素”的数组中


对于上面的情况无论是查找3还是查找8,只有一种办法,那就是遍历。时间复杂度是O(N)

但是举一个生活中的例子,我们在查新华字典的时候,没有人会一页一页的翻吧,肯定是翻到中间看看,然后再决定往左边查,还是往右边查。

显然,可以把元素排序放在一个数组中,这样就可以利用二分查找了。查字典也算是二分查找的一个实际例子。二分查找的时间复杂度是O(logN)


利用二分查找是有前提限制的:

1:元素已排序

2:元素可以随机访问


二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存。这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了。(当然如果删除操作较少的话,也可以用懒惰删除,被删除的元素做个标记,但并不实际从内存中清楚,所以也不用移动元素。但是对于插入还是没有办法)


所以,现在我们需要的是一个能够进行二分查找,又能快速添加和删除元素的数据结构。这就非(平衡)二叉查找树莫属了。

此时,我们可以利用树这个模型,对1,2,3,4,5,6这6个元素够造成一个二叉树,如下:


显然,上面这棵树太坑爹了。把自己的头右转45°或者把显示器左转45°,我们会发现,这TM就是一个链表。这棵树不符合快速查找元素的要求,原因就是,每个节点的左右子树之差>1,也就是不平衡。不平衡就导致了树的高度过高,所以没有办法根据一个节点筛选掉一半的子结点(左右子结点个数不相等)

然后,带有高度平衡条件的AVL树,是下面这样的。


对于这样一棵树,查找元素就是变相的二分查找。(插入,删除也有了保证,直觉上只需修改几个指针)

但是,由于AVL树要求高度平衡,不论是插入还是查找,被操作节点到根节点的整条路径上的所有节点都要判断,是否打破了平衡条件,从而LL, LR, RR, RL旋转。(当然了,实现一颗AVL树也不困难,但是相对于skiplist还是复杂些)

RB树是一种对平衡要求相对略低的AVL树。(类似于2-3树)

但是,无论是高度平衡的AVL还是RB树,自己编写起来难度都比较大。虽然说已经有了现成的set/map(基于红黑树实现)。


通过上面的几个图,我们可以发现,二分查找和AVL树为什么那么高效,原因就是:每比较一次,就能筛掉一半元素。

就像我们查字典一样,没有人会傻傻的从第一页开始一页一页的查,肯定是先翻到字典中间看看,然后决定继续查字典的前半部分还是后半部分。

接下来,我们就来看看跳跃表。

之所以成为跳跃表,就是因为每个节点会额外保存几个指针,间隔的指向后继的节点。



具体的查找,删除,插入原理。这里已经讲了。点击打开链接

我自己实现一个最高为12层的跳跃表,并与红黑树的效率做了对比。


可以看到,数据量很大时,跳跃表的优势显著(当然skiplist很浪费内存这个劣势也很明显)。


http://blog.csdn.net/qq575787460/article/details/16371287上一篇引入了跳跃表的介绍,并且测试了跳跃表的插入和查找效率。可以看到在大量数据中效率明显高于红黑树。(当然带来的空间的巨大浪费)。


那么跳跃表为什么会快呢?下来看一个生活中的例子。


如图,A--G分别代表250路公交车经过的车站。有特快,快和慢3条线路。

特快的只在A,E,G站牌停车。

快线只在A,C,E,H,G站牌停车。

慢线则在所有的站牌停车。

如果我想A站坐到I站,那么最快的乘车方案就是

A站牌---特快线---E站牌---换成快线---H站牌---换成慢线---I站牌。

是不是很像跳跃表?


并且在上一篇博客中测试情况看来,跳跃表与红黑树的查找效率差别不大,而跳跃表的插入效率明显高于红黑树。究其原因,跳跃表在插入的时候,只需要记下来插入一个节点需要更新哪一层的哪个节点就行了,而红黑树则需要回溯的对整个插入路径调整。


在插入节点时,我们是随机的给新节点一个层次高度,其实并不是随机。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int get_rand_lvl()  
  2. {  
  3.     int lvl = 1;  
  4.     while ((rand() & 0xffff) < (0xffff >> 2))  
  5.          lvl++;  
  6.     return lvl;  
  7. }  
关键是要理解while循环中的那一句是什么意思?

rand() & 0xffff 意思是随机一个0---0xffff之间的整数。

0xffff >> 2 意思是0xffff的四分之一

可以把while循环理解为产生一个0--1之间的小数,如果这个小数小于0.25,那么把层数+1,并且进行下一次while循环。

是不是感觉很诡异,为什么不直接随机一个1---MAX_LVL之间的数呢?

我想了好几天,终于知道了,这涉及到了层次分布的问题。

通过while循环来产生层次,那么层次越大,几率越低。

例如,lvl=1的概率为0.75(第一次while就为false)

lvl=2的概率为0.25

lvl=3的概率为0.25*0.25

lvl=4的概率为0.25*0.25*0.25

所以,如果数据量非常大,那么整个跳跃表看起来是波浪线非常明显的。

而如果直接随机出来一个高度,那么高度的分布均匀了,反而不利于查找。


跳跃表的源码点击打开链接


skip_list.h

#ifndef SKIP_LIST_H_
#define SKIP_LIST_H_

struct skip_list_node;

class skip_list
{
        public:
                explicit skip_list(int max_level = 12);
                ~skip_list();


                int insert(int data);
                bool find(int data) const;
                int remove(int data);
                int size() const;


        private:
                int rand_level();


        private:
                skip_list_node *header_;
                int size_;
                int max_level_;


        private:
                skip_list(const skip_list&);
                skip_list& operator= (const skip_list&);
};

#endif


skip_list.cpp


#include "skip_list.h"

#include <stdlib.h>
#include <time.h>

struct skip_list_node
{
        int data_;
        skip_list_node **forward_;
};

skip_list::skip_list(int max_level /*=12*/)
        : header_(NULL), size_(0), max_level_(max_level)
{
        this->header_ = new skip_list_node;
        this->header_->forward_ = new skip_list_node*[this->max_level_];
        for (int level = 0; level < this->max_level_; ++level)
                this->header_->forward_[level] = NULL;
}

skip_list::~skip_list()
{
        while (this->header_)
        {
                skip_list_node *tmp_node = this->header_;
                this->header_ = this->header_->forward_[0];
                delete []tmp_node->forward_;
                delete tmp_node;
        }
}

int skip_list::insert(int data)
{
        skip_list_node **update = new skip_list_node*[this->max_level_];
        skip_list_node *cur = this->header_;
        for (int k = this->max_level_ - 1; k >= 0; --k)
        {
                skip_list_node *next = NULL;
                while ((next = cur->forward_[k]) && (next->data_ < data))
                        cur = next;
                update[k] = cur;
        }

        bool b_find = (update[0]->forward_[0]
                        && update[0]->forward_[0]->data_ == data);
        if (!b_find)
        {
                skip_list_node *node = new skip_list_node;
                node->data_ = data;
                int new_node_level = this->rand_level();
                if (new_node_level > this->max_level_)
                        new_node_level = this->max_level_;
                node->forward_ = new skip_list_node*[new_node_level];
                for (int k = 0; k < new_node_level; ++k)
                {
                        node->forward_[k] = update[k]->forward_[k];
                        update[k]->forward_[k] = node;
                }
                ++this->size_;
        }

        delete []update;

        return b_find ? -1 : 0;
}
bool skip_list::find(int data) const
{
        bool b_find = false;
        skip_list_node *cur = this->header_;
        for (int k = this->max_level_ - 1; k >= 0; --k)
        {
                skip_list_node *next = NULL;
                while ((next = cur->forward_[k]) && next->data_ < data)
                        cur = next;
                if (next && next->data_ == data)
                {
                        b_find = true;
                        break;
                }
        }

        return b_find;
}

int skip_list::remove(int data)
{
        skip_list_node **update = new skip_list_node*[this->max_level_];
        for (int k = this->max_level_ - 1; k >= 0; --k)
        {
                skip_list_node *cur = this->header_;
                skip_list_node *next = NULL;
                while ((next = cur->forward_[k]) && next->data_ < data)
                        cur = next;
                update[k] = cur;
        }
        bool b_find = (update[0]->forward_[0]
                                                                && update[0]->forward_[0]->data_ == data);
        if (b_find)
        {
                skip_list_node *find_node = update[0]->forward_[0];
                for (int k = 0; k < this->max_level_; ++k)
                {
                        if (update[k]->forward_[k] == find_node)
                                update[k]->forward_[k] = find_node->forward_[k];
                        else
                                break;
                }
                delete []find_node->forward_;
                delete find_node;
                --this->size_;
        }

        return b_find ? 0 : -1;
}


int skip_list::size() const
{
        return this->size_;
}


int skip_list::rand_level()
{
        int level = 1;
        while ((::rand() & 0xffff) < (0xffff >> 2))
                ++level;

        return level;
}


test.cpp


#include "skip_list.h"


#include <sys/time.h>
#include <stdint.h>
#include <stdlib.h>
#include <iostream>
#include <set>


static int64_t current_ms();
int main(int argc, char *argv[])
{
        if (argc != 2)
                return 0;


        const int num = ::atoi(argv[1]);


        int64_t t1 = current_ms();
        skip_list *sl = new skip_list;
        for (int i = 0; i < num; ++i)
                if (sl->insert(i) != 0)
                        std::cout << "skip_list insert " << i << " failed." << std::endl;
        int64_t t2 = current_ms();
        std::cout << "skip_list insert used " << t2 - t1 << " ms" << std::endl;
        int64_t t3 = current_ms();
        for (int i = 0; i < num; ++i)
                if (!sl->find(i))
                        std::cout << "skip_list not found " << i << std::endl;
        int64_t t4 = current_ms();
        std::cout << "skip_list find used " << t4 - t3 << " ms" << std::endl;
        delete sl;


        std::set<int> si;
        int64_t t5 = current_ms();
        for (int i = 0; i < num; ++i)
                si.insert(i);
        int64_t t6 = current_ms();
        std::cout << "rb_tree insert used " << t6 - t5 << " ms" << std::endl;
        int64_t t7 = current_ms();
        for (int i = 0; i < num; ++i)
                if (si.find(i) == si.end())
                        std::cout << "rb_tree not found " << i << std::endl;
        int64_t t8 =current_ms();
        std::cout << "rb_tree find used " << t8 - t7 << " ms" << std::endl;
        return 0;
}


static int64_t current_ms()
{
        struct timeval tv;
        ::gettimeofday(&tv, NULL);
        return int64_t(tv.tv_sec) * 1000 + tv.tv_usec / 1000;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值