跳跃表

跳跃表是一个挺特殊的数据结构,它是基于有序链表的二次开发。对于普通的有序链表,我们查找它元素的平均时间复杂度为redis set getall 时间复杂度 redis del时间复杂度_链表,当我们使用二分查找树,时间则是redis set getall 时间复杂度 redis del时间复杂度_跳跃表_02,这里我们不考虑不平衡的情况。

程序猿就想到了,我们在链表插入数据的时候,就应该让链表保持有序,这样插入的时间复杂度就是redis set getall 时间复杂度 redis del时间复杂度_链表

redis set getall 时间复杂度 redis del时间复杂度_redis_04


保持有序之后可以干什么呢?我们可以增加一个字段,这个字段保存了跳往下下个节点的信息。比如我现在想找60这个节点,原本我需要查找3次,如果我可以跳过一个节点,只需要查找两次。当数据量再大的时候,我们查找的次数就是redis set getall 时间复杂度 redis del时间复杂度_链表_05

redis set getall 时间复杂度 redis del时间复杂度_Redis_06


本来找到140这个节点需要8次,现在只需要4次。可不可以再快点?可以。

redis set getall 时间复杂度 redis del时间复杂度_redis_07


这样只需要3次了。但是这样的结构,我们无法构造结构体或者类去实现,因为我们不知道数据量的大小,更不知道跳几次才是最好的。因此,程序猿们想到了一种新的结构可以替代上面的结构:

redis set getall 时间复杂度 redis del时间复杂度_Redis_08


这样一来,每个节点只需要保存3个元素:数据,节点后面的值,节点下面的值。

public class ListNode {
	int value;
	ListNode nextNode;
	ListNode downNode;
}

然后,我们需要特殊定义一个头结点,保存一个极小值,再定义一个尾节点,保存一个极大值,这样可以利于程序的实现。单链表是可以实现的,但是我比较菜,插入算法搞了好久出不来,所以我决定实现双链表吧,算法写起来相对简单点。所以最终这个类就变成了:

public class Node {
	public int value;
	public Node left;
	public Node right;
	public Node up;
	public Node down;
	public int level; //记录当前节点的层级
}
public class SkipList {
	private int n;  //记录表中元素个数
	private Node head;   //记录头结点位置
	private Node tail;  //记录尾节点的位置
	private int level;  //记录层数
}

跳跃表的实现

当我考虑了两天,想到怎么实现跳跃表的时候,我发现我并不能很好的实现跳跃表。正如跳跃表的思想是跳跃,这个跳跃在官方的实现是随机的,如果不随机,性能便会造成一定的下降。我们先看看我的代码:

private void rearrange(Node node) throws Exception {
		if(node.level != 1) {
			throw new Exception("insert error!");
		}
		Node p;
		while(node.right != null) {
			if(node.up != null) {
				node.up.setDown(node.left);
				node.left.setUp(node.up);
				node.setUp(null);
				p = node.left.up;
				while(p != null) {
					p.setValue(p.down.value);
					p = p.up;
				}
			}
			node = node.right;
		}
		if(node.up == null && node.left.up == null) {
			p = node.left.left;
			if(p.up != null) {
				Node _node = new Node(node.value, p, null, null, node, node.level + 1);
				p = p.up;
				p.setRight(_node);
			}
		}
	}

这是不完整的代码,因为我没有考虑进去增加层数,补充不完整层的情况。按照理论,我们需要从头到尾,依次判断某个节点是否应该有上节点。但是,如此判断时间复杂度就成了redis set getall 时间复杂度 redis del时间复杂度_跳跃表_09明显与我们设计的跳跃表不符合。所以我没有想到随机数添加层以外的其他做法。

唉,我只能说没有能够实现跳跃表,我不想做Random的跳跃表了。这理我把查找的思想写下来吧:

public Node findByValue(int value) {
		Node temp = this.head.right;
		Node record = this.head;
		while(true) {
			if(temp.value < value && temp.right != null) {
				temp = temp.right;
				record = record.right;
			}else if(temp.value > value && temp.down != null){
				temp = record.down.right;
				record = record.down;
			}else if(temp.value == value){
				while(temp.down != null) {
					temp = temp.down;
				}
				return temp;
			}else {
				return null;
			}
		}
		
	}
public Node findByIndex(int index) throws Exception {
		
		if(head == null) {
			throw new Exception(new String("uninitialized skiplist head"));
		}
		if(index < 1 || index > this.n) {
			throw new Exception(new String("index out of range"));
		}
		
		int jump_distance = 1 << (head.level - 1);
		Node temp = this.head;
		if(index == jump_distance) {
			temp = temp.right;
			while(temp.down != null) {
				temp = temp.down;
			}
			return temp;
		}else if(index < jump_distance) {
			while(true) {
				if(index < jump_distance) {
					temp = temp.down;
					jump_distance = jump_distance >> 1;
				}else if(index > jump_distance) {
					temp = temp.right;
					index = index - jump_distance;
				}else {
					temp = temp.right;
					while(temp.down != null) {
						temp = temp.down;
					}
					return temp;
				}
			}
		}else {
			int i = 0;
			while(true) {
				if(index > jump_distance) {
					temp = temp.right;
					index = index - jump_distance;
				}else if(index < jump_distance) {
					temp = temp.down;
					jump_distance = jump_distance >> 1;
				}else {
					temp = temp.right;
					while(temp.down != null) {
						temp = temp.down;
					}
					return temp;
				}
			}
			
		}
	}

行,这个系列先不结尾,最后我再写个Redis新特性的文章作为结尾,估计比较短,过两天上。