跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。
第一层节点个数为 n,位于第 i 层的节点有 p 的概率出现在第 i+1 层,p为常数。假设共有 m 层
空间复杂度为O(n)
跳表的查询,插入和删除操作的期望时间复杂度都为O(logn)
为了计算查找长度,我们将逆向还原查找过程,从右下方第一层最后到达的那个节点开始,沿着查找路径向左,向上回溯。假设当回溯到某个节点的时候,它才被插入。
- 如果节点x有第i+1,那么我们需要向上走,这种情况概率为p
- 如果节点没有第i+1层指针,那么我们需要向左走,这种情况的概率为1-p
用C(k)表示向上攀爬k个层级所需要走过的平均查找路径长度,因此有
思路:
查找过程中,每比较一次要么向下一层,要么到本层的右侧节点。每个节点在进行插入的时候,它的层数是由随机函数randomLevel()计算出来的,随机的计算不依赖于其它的节点,每次插入过程都是完全独立的。
位于第 i 层的节点有 p 的概率出现在第 i+1 层
redis中,p = 1/4,MaxLevel = 32
class Node{
Integer value; //节点值
Node[] next; // 节点在不同层的下一个节点
// 用size表示当前节点在跳表中索引几层
// 比如7在第四层的,那第四层下面共有4个7
public Node(Integer value,int size) {
this.value = value;
this.next = new Node[size];
}
}
- 先随机出来一个层数,new要插入的节点,先叫做插入节点newNode;
- 根据跳表实际的总层数从上往下分析,要插入一个节点newNode时,先找到节点在该层的位置:因为是链表,所以需要一个节点node,满足插入插入节点newNode的值刚好不大于node的下一个节点值,当然,如果node的下个节点为空,那么也是满足的。
- 确定插入节点newNode在该层的位置后,先判断下newNode的随机层数是否小于当前跳表的总层数,如果是,则用链表的插入方法将newNode插入即可。
- 如此循环,直到最底层插入newNode完毕。
- 循环完毕后,还需要判断下newNode随机出来的层数是否比跳表的实际层数还要大,如果是,直接将超过实际层数的跳表的头节点指向newNode即可,该跳表的实际层数也就变为newNode的随机层数了。
class Skiplist {
//最大层数
private static int DEFAULT_MAX_LEVEL = 32;
//随机层数概率,也就是随机出的层数,在第1层以上(不包括第一层)的概率,
//层数不超过maxLevel,层数的起始号为1
private static double DEFAULT_P_FACTOR = 0.25;
Node head = new Node(null,DEFAULT_MAX_LEVEL); //头节点
int currentLevel = 1; //表示当前nodes的实际层数,它从1开始
public Skiplist() {}
public boolean search(int target) {
Node searchNode = head;
for (int i = currentLevel-1; i >=0; i--) {
searchNode = findClosest(searchNode, i, target);
if (searchNode.next[i]!=null && searchNode.next[i].value == target){
return true;
}
}
return false;
}
public void add(int num) {
int level = randomLevel();
Node updateNode = head;
Node newNode = new Node(num,level);
// 计算出当前num 索引的实际层数,从该层开始添加索引
for (int i = currentLevel-1; i>=0; i--) {
//找到本层最近离num最近的list
updateNode = findClosest(updateNode,i,num);
if (i<level){
if (updateNode.next[i]==null){
updateNode.next[i] = newNode;
}else{
Node temp = updateNode.next[i];
updateNode.next[i] = newNode;
newNode.next[i] = temp;
}
}
}
if (level > currentLevel){ //如果随机出来的层数比当前的层数还大,那么超过currentLevel的head 直接指向newNode
for (int i = currentLevel; i < level; i++) {
head.next[i] = newNode;
}
currentLevel = level;
}
}
public boolean erase(int num) {
boolean flag = false;
Node searchNode = head;
for (int i = currentLevel-1; i >=0; i--) {
searchNode = findClosest(searchNode, i, num);
if (searchNode.next[i]!=null && searchNode.next[i].value == num){
//找到该层中该节点
searchNode.next[i] = searchNode.next[i].next[i];
flag = true;
continue;
}
}
return flag;
}
//找到level层, value 大于node 的节点
private Node findClosest(Node node,int levelIndex,int value){
while ((node.next[levelIndex])!=null && value >node.next[levelIndex].value){
node = node.next[levelIndex];
}
return node;
}
//随机一个层数
private static int randomLevel(){
int level = 1;
while (Math.random()<DEFAULT_P_FACTOR && level<DEFAULT_MAX_LEVEL){
level ++ ;
}
return level;
}
class Node{
Integer value;//节点值
Node[] next;// 节点在不同层的下一个节点
public Node(Integer value,int size) {// size表示当前节点在跳表中索引几层
this.value = value;
this.next = new Node[size];
}
}
}