跳跃表(skip list)的原理

跳表是一种优化链表访问的数据结构,通过增加多级索引,实现更快的查找效率。相比传统的链表,跳表可以达到O(log n)的查找时间复杂度,而插入和删除操作也保持了链表的灵活性。文章介绍了跳表的基本原理,包括如何通过随机概率确定新节点的层数,并提供了简单的实现概述。

学过编程的都知道,数据结构中有一种数据结构叫做链表。

它的简单实现如下:

struct Node {
	T v;
	Node* nxt;
};

struct List {
	Node Head;
};

它的简单示意图如下:
在这里插入图片描述

链表有什么特点呢?

它一般是与数组比的,为什么呢,因为数组的优点就是链表的缺点,链表的优点就是数组的缺点。它们很有比较性。

链表是不连续的内存,所以新增元素不用改变原来已有元素的内存位置,很方便(跟数组比)。

但是,它访问很慢,因为单纯的链表这种结构访问元素的方式很笨重,就是一个一个访问。然而数组就很快了,由于它是连续的内存,所以能很快算出下一个元素的位置。如果我们需要某个确定索引位置的元素,数组比链表快。

想一下链表访问慢的原因。

是因为它的访问方式决定了它访问很慢,因为它一次访问唯一的一个元素(这个元素一般就是下一个元素)。因为它一个结点只能访问一个其他的结点。

那么,可不可以一次可以访问多个元素呢?

能不能一次访问两个呢,或者一次访问三个?

当然是可以的。

这就是跳表。

下图示意一下什么是跳表。

在这里插入图片描述

先看到,跳表的最底层其实就是链表(一个元素接着一个)。然后上面那么一层层的结点,是什么意思呢?

就是跳跃的意思,就是打破单个链表结点一次只能访问一个结点带来的慢速度。

拿a这个节点来说明一下。

(请注意把最底层的结点和它上面一层一层的结点看成一个整体。)

a这个节点可以访问到三个元素,是b,c,e。分别跳了1步,2步,4步。
b这个节点可以访问到一个元素,是c。跳了1步。
c这个节点可以访问到两个元素,是c,e。分别跳了1步,2步。

所以此时我们从头节点访问元素的步骤如下:

访问的节点跳表的路径跳表所需访问元素的个数链表的路径链表所需访问元素的个数
aa1a1
ba->b2a->b2
ca->c2a->b->c3
da->c->d3a->b->c->d4
ea->e2a->b->c->d->e5
fa->e->f3a->b->c->d->e->f6
ga->e->g3a->b->c->d->e->f->g7

如上图,相比较而言是不是快了。

跳表的时间复杂度是O(log n),比链表O(n)快多了。

感觉跟树状数组有点像。就是一个是数组,一个是链表。

那么跳表新增一个结点的时候,需要确定它的层数,如何确定新增节点的层数?

我们容易知道,一个跳表,有n/2的结点只有一层,有n/4的结点有两层,有n/8的结点有三层。

根据理论上的统计我们确定一个结点层数的概率就是,对于一个未知结点。它有1/2的可能是一层,1/4的可能是两层,1/8的可能是三层。。。

所以对于一个新增结点,我们随机给层数。

下面是它的简单实现:

/// @file skip_list.cpp
/// @brief wait for check
/// @author zhaolu
/// @version 1.0
/// @date 2020-03-07
/// @copyright Copyright (c) 2020 zhaolu. All Rights Reserved.

#include <string>
#include <random>
#include <iostream>

template<typename T>
class node {
	private:
		unsigned random_level() const {
			std::random_device rd; 
			std::mt19937 generator(rd());
		   	unsigned level = 1;

		   	for (int i = 1; i < 16; ++i) {
		    	if (generator() % 2 == 1) {
		    		++level;
		    	} 
			}
		    	
		    return level;
		}
	public:
		node(T value) : _value(value) {
			_level = random_level();
			_nxt = (node<T>**)malloc(sizeof(node<T>*) * _level);
			memset(_nxt, 0x00, sizeof(node<T>*) * _level);
		}
		
		virtual ~node() {
			free(_nxt);
		}

		node<T>** at(unsigned level) const {
			return (level >= _level) ? nullptr : &_nxt[level];
		}

		T _value;
		const unsigned _level;
		node<T>** _nxt;
};

template<typename T>
class skip_list {
public:
	static const unsigned MAX_LEVEL = 16;
private:
	unsigned _size;
	node<T>* _head;
	
public:
	skip_list() : _size(0), _head(new node<T>(-1)) {}

	node<T>* find_last_lower(T value) const {
		if (_head == nullptr || _head->_value >= value)
			return nullptr;

		node<T>* p = _head;
		for (int i = MAX_LEVEL - 1; i >= 0; --i) {
			while (p->at(i) != nullptr && *(p->at(i))->_value < value)
				p = *(p->at(i));
		}

		return p;
	}

	bool contains(T value) const {
		node<T>* p = find_last_lower(value);
		return p != nullptr && *(p->at(0))->_value == value;
	}
	
	node<T>* insert(T value) {
		++_size;
		node<T>* new_node = new node<T>(value);
		node<T>* p = _head;

		node<T>** update_node = new node<T>[new_node->level];
		for(int i = new_node->_level - 1; i >= 0; --i) {
			while (p->at(i) != nullptr && *(p->at(i))->_value < value)
				p = *(p->at(i));
			update_node[i] = p;
		}

		for(int i = new_node->_level - 1; i >= 0; --i) {		
			*(new_node->at(i)) = *(update_node[i].at(i));
			*(update_node[i].at(i)) = new_node;
		}

		delete[] update_node;
	}

	int erase(T value) {
		node<T>* delete_node = new node<T>[MAX_LEVEL];
		node<T>* p = _head;
		for(int i = MAX_LEVEL - 1; i >= 0; --i) {
			while (p->at(i) != nullptr && *(p->at(i))->_value < value)
				p = *(p->at(i));
			delete_node[i] = p;
		}

		if (p->at(0) == nullptr || *(p->at(0))->_value != value)
			return -1;

		for(int i = MAX_LEVEL - 1; i >= 0; --i) {
			if (delete_node[i] != nullptr && *(delete_node[i].at(i))->_value == value)
				*(delete_node[i].at(i)) = *(*(delete_node[i].at(i)).at(i));
		}

		delete[] delete_node;
	}

	virtual ~skip_list() {
		node<T>* p = _head;
		while (p != nullptr) {
			node<T>* nxt = *(p->at(0));
			delete p;
			p = nxt;
		}
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值