目录:
Redis从入门到精通(一):缓存
Redis从入门到精通(二):分布式锁以及缓存相关问题
Redis从入门到精通(三):Redis持久化算法以及内存淘汰策略
Redis从入门到精通(四):Redis常用数据结构以及指令
Redis从入门到精通(五):Redis6整合SpringBoot2.x+Mybatis+SpringCache
Redis从入门到精通(六):Redis高可用原理
Redis从入门到精通(七):跳跃表的简介与实现
Redis从入门到精通(八):Redis新特性
跳跃表
跳跃表是一个挺特殊的数据结构,它是基于有序链表的二次开发。对于普通的有序链表,我们查找它元素的平均时间复杂度为 O ( n ) O(n) O(n),当我们使用二分查找树,时间则是 O ( l o g n ) O(logn) O(logn),这里我们不考虑不平衡的情况。
程序猿就想到了,我们在链表插入数据的时候,就应该让链表保持有序,这样插入的时间复杂度就是
O
(
n
)
O(n)
O(n)。
保持有序之后可以干什么呢?我们可以增加一个字段,这个字段保存了跳往下下个节点的信息。比如我现在想找60这个节点,原本我需要查找3次,如果我可以跳过一个节点,只需要查找两次。当数据量再大的时候,我们查找的次数就是
O
(
n
/
2
)
O(n/2)
O(n/2)
本来找到140这个节点需要8次,现在只需要4次。可不可以再快点?可以。
这样只需要3次了。
但是这样的结构,我们无法构造结构体或者类去实现,因为我们不知道数据量的大小,更不知道跳几次才是最好的。因此,程序猿们想到了一种新的结构可以替代上面的结构:
这样一来,每个节点只需要保存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);
}
}
}
这是不完整的代码,因为我没有考虑进去增加层数,补充不完整层的情况。按照理论,我们需要从头到尾,依次判断某个节点是否应该有上节点。但是,如此判断时间复杂度就成了 O ( N + l e v e l ) O(N+level) O(N+level)明显与我们设计的跳跃表不符合。所以我没有想到随机数添加层以外的其他做法。
唉,我只能说没有能够实现跳跃表,我不想做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新特性的文章作为结尾,估计比较短,过两天上。