Skip List(跳表)

1. 跳表产生的背景

我们知道,通常意义上的链表是不能支持随机访问的(即不能通过索引快速定位),其查找的时间复杂度是O(n) ,而数组这一可支持随机访问的数据结构,虽然查找很快,但是插入和删除元素却需要移动插入点后的所有元素,时间复杂度为O(n) 。

为了解决这一问题,引入了树结构,树的增删改查效率比较平均,一棵平衡二叉树(AVL)的增删改查效率一般为O(logn) ,比如工业上常用红黑树作为AVL的一种实现。

但是,AVL的实现一般都比较复杂,插入/删除元素可能涉及对整个树结构的修改,特别是并发环境下,通常需要全局锁来保证AVL的线程安全,于是又出现了一种类似链表的数据结构——跳表。在高并发环境下,在进行插入和删除时,跳表只需要对整个数据结构的部分进行加锁即可。

2. 什么是Skip List

在传统的单链表结构中,查找某个元素需要从链表的头部按顺序遍历,直到找到目标元素为止,查找的时间复杂度为O(n)。

上图的单链表中(省去了结点之间的链接),当想查找7、15这三个元素时,必须从头指针head开始,遍历整个单链表,其查找的效率是比较低。

因此对于具有n个元素的链表,我们可以采取(logn+1)层指针路径的形式,就可以实现在 O(logn)的时间复杂度内,查找到某个目标元素了,这种数据结构,我们也称之为跳表,跳表也可以算是链表的一种变形,只是它具有二分查找的功能。(另外一种说法就是跳表是一种在有序链表的基础之上建立多层索引,使其具有类似于二分查找的特性,可以快速访问到元素的数据结构

下图是一种可能的跳表结构:

如图,[1][40]节点有3层,[8][18]节点有2层,每一层都是有序的链表。

如果要查找目标节点[15],大致过程如下:

1. 先查看[1]节点的最顶层,发现[1]节点的下一个节点为[40],大于15,那么查找[1]节点的下一层;

2. 查找[1]节点的第2层,发现[1]节点的下一个节点为[8],小于15,接着查看下一个节点,发现下一个节点是[18],大于15,因此查找[8]节点的下一层;

3. 查找[8]节点的第2层,发现[8]节点的下一个节点是[10],小于15,接着查看下一个节点[13],小于15,接着查看下一个节点[15],发现其值等于15,因此找到了目标节点,结束查询。

跳表维护了多层级的链表,属于一种“空间换时间”的算法设计思想

3. 一个结点可以包含多少层呢

例如Skip List也可能是下面这种包含3层的结构(在一个3层Skip List中查找元素“46”):  

跳表建立的索引层数是根据一种随机算法得到的,为了不让层数过大,还会有一个最大层数MAX_LEVEL限制,随机算法生成的层数不得大于该值。

public final int MAX_LEVEL = 4;

public int getRandom() {
	int k = 1;
	Random random = new Random();
	while (random.nextBoolean() && k < MAX_LEVEL) {
		k++;
	}
	return k;
}

4. 总结

跳表是一种类似于链表的数据结构,结合了树和链表的特点,查询、插入、删除的时间复杂度都是O(logn) ,其特性如下:

1. 跳表由很多层组成

2. 每一层都是一个有序的链表

3. 最底层的链表包含所有元素

4. 每个节点不仅仅包含指向下一个节点的指针,还可能包含指向其下一层的指针

5. 如果一个元素出现在第n(n>1)层的链表中,则它在n层以下的链表也都会出现

如果我们需要集合是有序的并且满足高并发场景,那么跳表就是不二的选择,JDK中实现这一数据结构的类是ConcurrentSkipListMap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值