跳表

 

                                                                     跳表

定义:

查找:

插入:

删除:

打印:

其他函数:

复杂度:


定义:

跳跃表(英文名:Skip List),于 1990 William Pugh 发明,是一个可以在有序元素中实现快速查询的数据结构,其插入,查找,删除操作的平均效率都为 O(logn)

跳跃表的整体性能可以和二叉查找树(AVL 树,红黑树等)相媲美,其在 Redis  LevelDB 中都有广泛的应用。

每个结点除了数据域,还有若干个指针指向下一个结点。

整体上看,Skip List 就是带有层级结构的链表(结点都是排好序的),最下面一层(level 0)是所有结点组成的一个链表,依次往上,每一层也都是一个链表。不同的是,它们只包含一部分结点,并且越往上结点越少。仔细观察你会发现,通过增加层数,从当前结点可以直接访问更远的结点(这也就是 Skip List 的精髓所在),就像跳过去一样,所以取名叫 Skip List(跳跃表)。

根据上诉的描述,我们可以写出如下的定义:

struct Node    //定义结点
{
	int key;   //关键值
	Node ** forward;   //保存了指向若干结点的指针,用二维数组保存。
	Node(int key = 0, int level = MAX_LEVEL)   //初始化一个新的结点
	{
		this->key = key;
		forward = new Node*[level];
		memset(forward, 0, level * sizeof(Node*));
	}
};

class SkipList
{
private: 
	Node * header;                           //头结点
	int level;                               //层高
private:
	int random_level();                      //随机值函数
public:
	SkipList();
	~SkipList();
	bool insert(int key);                    //添加
	bool find(int key);                      //查找
	bool erase(int key);                     //删除
	void print();                            //打印
};

查找:

例如上图,我们需要在这个跳表中去查找20.

1.我们从头结点(header)的最上层开始,第一个是17<20,我们就继续往右走,右一个是NULL,

2.我们就从17往下走一步,右一个是25>20,就从17那往下走一步,

3.来到了最后一层,17的右边是20,正好是我们查询的数据,返回。

代码如下:

bool SkipList::find(int key)
{
	Node * node = header;

	for (int i = level - 1; i >= 0; i--)
	{
		while (node->forward[i] && node->forward[i]->key <= key)
			node = node->forward[i];

		if (node->key == key)
			return true;
	}

	return false;
}

插入:

例如上图:我们要插入20.

1.首先我们要查找到每一层在20前面的数据,

2.构造数据为20的这个节点,

3.连接20前后的节点。

补充:

    在跳跃表中,有一个理想的跳表,就是说,在底层是数据的全部节点,每往上一层就是 下一层的结点。简而言之就是,如果我们在第0层右12个结点,那么第1层就是6个结点,第2层就是3个结点,以此类推,并且这些结点必须是均匀分布的,这时候整个表的层数就是logn。

但是,我们在平时的操作中,并不能作到如此的均匀,这时候我们每插入一个新的结点的时候,我们要尽量让他接近到理想的状态。因此提出了一个根据概率随机为新结点生成一个层数的方法:

 

  1. 给定一个概率 pp 小于 1),产生一个 [0,1) 之间的随机数;
  2. 如果这个随机数小于 p,则层数加 1
  3. 重复以上动作,直到随机数大于概率 p(或层数大于程序给定的最大层数限制)。

随机数的代码如下: 我们设置的概率是0.5,即 这个结点的的层数是1的概率是1/2,层数为2的概率是1/4,,,依次类推,我们设置了一个最大的层数是32,也可以不要这个层数。

#define P 0.25
#define MAX_LEVEL 32

int SkipList::random_level()
{
	int level = 1;

	while ((rand() & 0xffff) < (P * 0xffff) && level < MAX_LEVEL)
		level++;

	return level;
}

根据这个我们就可以写出了我们的插入函数如下:

bool SkipList::insert(int key)
{
	Node * node = header;
	Node * update[MAX_LEVEL];
	memset(update, 0, MAX_LEVEL * sizeof(Node*));
    //先找到每一层中,待插入key的前驱结点,将其保存中update中。
	for (int i = level - 1; i >= 0; i--)
	{
		while (node->forward[i] && node->forward[i]->key < key)
			node = node->forward[i];
		update[i] = node;
	}
    //node就是最底层key的前驱结点
	node = node->forward[0];
    //判断是否存在key
	if (node == nullptr || node->key != key)
	{
    //得到新结点key的层数
		int new_level = random_level();
    //如果层数大于目前最大的层数,则将头结点,保存到update中,也就是提升了头结点的层数
		if (new_level > level)
		{
			for (int i = level; i < new_level; i++)
				update[i] = header;

			level = new_level;
		}
     //产生一个新的结点key。
		Node * new_node = new Node(key, new_level);
    //将每一层中,key的前驱结点和key的后继结点连接起来,简单的链表中的结点插入。
		for (int i = 0; i < new_level; i++)
		{
			new_node->forward[i] = update[i]->forward[i];
			update[i]->forward[i] = new_node;
		}

		return true;
	}

	return false;
}

删除:

例如上图,我们要删除结点9,

1.查找到每一层9的前驱结点,

2.前驱结点连接到9的后记结点,

3.删除掉9的结点。如果9的结点时最高层,则删除一层。

代码如下:

bool SkipList::erase(int key)
{
	Node * node = header;
	Node * update[MAX_LEVEL];
	fill(update, update + MAX_LEVEL, nullptr);
    //查找到目标结点的每一层前驱结点,保存到update中。
	for (int i = level - 1; i >= 0; i--)
	{
		while (node->forward[i] && node->forward[i]->key < key)
			node = node->forward[i];
		update[i] = node;
	}
    //node为最下层目标结点
	node = node->forward[0];
    //如果最底层没有,则表示不存在,有的话,就进行删除
	if (node && node->key == key)
	{
    //将每一层中目标结点的前驱结点和目标结点的后继结点连接起来。
		for (int i = 0; i < level; i++)
		if (update[i]->forward[i] == node)
			update[i]->forward[i] = node->forward[i];
    //删除掉目标结点
		delete node;
    //判断是不是目标结点是不是最高层,删除掉
		for (int i = level - 1; i >= 0; i--)
		{
			if (header->forward[i] == nullptr)
				level--;
			else
				break;
		}
		return true;
	}

	return false;
}

打印:

直接遍历每一层,打印出全部的结果。

void SkipList::print()
{
	Node * node = nullptr;

	for (int i = 0; i < level; i++)
	{
		node = header->forward[i];
		cout << "Level " << i << " : ";
		while (node)
		{
			cout << node->key << " ";
			node = node->forward[i];
		}
		cout << endl;
	}

	cout << endl;
}

其他函数:

1.构造函数:

SkipList::SkipList()
{
	header = new Node;
	level = 0;
}

2.析构函数:

SkipList::~SkipList()
{
	Node * cur = header;
	Node * next = nullptr;

	while (cur)
	{
		next = cur->forward[0];
		delete cur;
		cur = next;
	}

	header = nullptr;
}

3.测试代码:

int main()
{
	SkipList sl;
	sl.insert(3);
	sl.insert(0);
	sl.insert(1); sl.insert(1);
	sl.insert(4);
	sl.insert(2); sl.insert(2);
	sl.insert(5);
	sl.insert(6);
	sl.print();
	cout << sl.find(50) << endl;
	cout << sl.find(2) << endl;
	cout << sl.find(7) << endl << endl;
	sl.erase(1);
	sl.print();
	sl.erase(4);
	sl.print();

	return 0;
}

结果如下:

复杂度:

1.时间复杂度:  查找的时间复杂度为O(logn),增加和删除的操作也是查找之后的链表结点的直接操作,因此,跳表的增删查的时间复杂度为O(logn);

2.空间复杂度: 最底层时N,往上依次递减,则结果为 n+n/2+n/4.......+1  因此结果为O(n).

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值