http://blog.csdn.net/qq575787460/article/details/16898045
版权声明:本文为博主原创文章,未经博主允许不得转载。
如何在大量元素中去查找某个元素。例如在下面的“大量元素”的数组中
对于上面的情况无论是查找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站牌。
是不是很像跳跃表?
并且在上一篇博客中测试情况看来,跳跃表与红黑树的查找效率差别不大,而跳跃表的插入效率明显高于红黑树。究其原因,跳跃表在插入的时候,只需要记下来插入一个节点需要更新哪一层的哪个节点就行了,而红黑树则需要回溯的对整个插入路径调整。
在插入节点时,我们是随机的给新节点一个层次高度,其实并不是随机。
- int get_rand_lvl()
- {
- int lvl = 1;
- while ((rand() & 0xffff) < (0xffff >> 2))
- lvl++;
- return lvl;
- }
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;
}