skiplist的原理及简单实现
起因是因为问到了redis中sort_set的底层实现,OK我们当然知道是跳表了,首先我们看看skiplist的数据结构吧。
skiplistnode
这个我们可以看一下源码的底层结构设计是怎样的。
typedef struct zskiplistnode{ //后退指针 struct zskiplistnode *backward; //分值,用于排序用 double score; //成员对象 robj *obj; //层 struct zskiplistLevel{ //前进指针 struct zskiplistnode *forward; //跨度,根据跨度可以确定自己的位置 unsigned int span; }level[]; }zskiplistnode;
解释一个各个变量的作用,跳表是一种空间换时间的数据结构,层数越多,我们访问节点的速度就越快。
- 前进指针:用于从表头方向向表尾方向访问节点。
- 跨度:记录两个节点之间的距离,可以通过跨度确定节点的位置。
- 后退指针:用于从表尾方向向表头方向访问节点。
- 分值和成员:分值是用于排序的变量,成员一般是一个sds字符串。
以上就是skiplistnode的数据结构了,当然我们还有一个skiplist的数据结构
skiplist
typedef struct zskiplist{ //指向头节点和尾节点,头节点通常是一个32层的节点,尾节点则是有数据的 struct skiplistnode *head,*tail; //表中节点数量 unsigned long length; //表中层数最大的节点的层数 int level; }zskiplist
skiplist简单实现
class Skiplist { Node head = new Node(null, null, 0); Random rand = new Random(); Node[] stack = new Node[64]; static class Node { int val; Node right, down; public Node(Node r, Node d, int val) { right = r; down = d; this.val = val; } } }
skiplist的查找
我们如何进行查找的呢,一般是从最高层开始,向右向下进行查找,给出简单的实现。
public boolean search(int target) { // 先往右再往下,缩小区间,套路都是这个套路 for (Node p = head; p != null; p = p.down) { while (p.right != null && p.right.val < target) { p = p.right; } if (p.right != null && p.right.val == target) { return true; } } return false; }
skiplist的插入
插入的时候我们需要保存遍历的每个最右节点。保存之后然后回溯的插入节点,还需要根据幂次定律,即抛硬币的方式决定是否插入(代码中体现为rand.nextInt()&1)。如果说可以抛到最上层了,结束还可以再插入一层,那么我们需要创建新的一层。一般来说不超过32。
public void add(int num) { int lv = -1; for (Node p = head; p != null; p = p.down) { while (p.right != null && p.right.val < num) { p = p.right; } stack[++lv] = p; } boolean insertUp = true; Node downNode = null; while (insertUp && lv >= 0) { Node insert = stack[lv--]; insert.right = new Node(insert.right, downNode, num); downNode = insert.right; insertUp = (rand.nextInt() & 1) == 0; } if (insertUp) { head = new Node(new Node(null, downNode, num), head, 0); } }
skiplist的节点删除
删除方法较为简单,从最上面一层开始遍历即可,一层删除完了就遍历下一层。
public boolean erase(int num) { boolean exists = false; for (Node p = head; p != null; p = p.down) { while (p.right != null && p.right.val < num) { p = p.right; } if (p.right != null && p.right.val <= num) { exists = true; p.right = p.right.right; } } return exists; }