跳表
本文最后更新于:2024年2月12日 晚上
跳表:SkipList
跳表是一种概率数据结构,用于有序集合的快速搜索查找,支持$O(logn)$时间复杂度的插入、查找、删除。在性能上与红黑树、AVL树相当,但是在实现上比这二者简单,redis中zset的实现使用了这种数据结构。
底层结构:链表(多级链表)

简单实现
Redis: zset源码
1.定义skiplist节点
class Node {
    int val; // 节点存储的值
    Node[] next; // 保存节点在对应层的下一个节点的指针
    
    public Node(int val, int level) {
        this.val = val;
        this.next = new Node[level]; // 表示当前节点有level层
    }
}2.定义常量
// skiplist 头节点
private Node head;
// 当前跳表的最大层数
private int currentLevel = 1;
// skiplist的最大层数:可以满足2^64个元素的存储
private static final int SKIPLIST_MAX_LEVEL = 32;
// 节点是否进行层数增长的概率阈值
private static final double SKIPLIST_P = 0.25;3.定义两个辅助函数
getRandomLevel
在新建节点时,通过“摇骰子”的方式确定新建节点的层数
private int getRandomLevel() {
    int level = 1;
   	// 只要“摇骰子”的结果大于阈值,并且level小于最大层数,就一直增大当前节点的层数
    while (Math.random() > SKIPLIST_P && level < SKIPLIST_MAX_LEVEL) {
        level++;
    }
    return level;
}getClosestNode
找到在指定层idx中,于要查找值val最接近的节点
idx:指定的层id
val:要查找的值
private Node getClosestNode(Node node, int idx, int val) {
    while (node.next[idx] != null && val > node.next[idx].val) {
        node = node.next[idx];
    }
    return node;
}4.insert:插入操作
允许插入重复值。插入时根据新建节点的层数,进行逐层插入。
public void insert(int val) {
    int level = getRandomLevel();
    Node temHead = head;
    Node node = new Node(val, level); // 要插入的新节点
    for (int i = currentLevel - 1; i >= 0; i--) {
        temHead = getClosestNode(temHead, i, val);
        // 当前层数小于新建节点的层数
        if (i < level) {
            if (temHead.next[i] == null) {
                // 该节点是尾节点,直接将新节点插到最后
                temHead.next[i] = node;
            } else {
                // 否则将新节点插到前后两个节点中间
                node.next[i] = temHead.next[i];
                temHead.next[i] = node;
            }
        }
    }
    // 如果新建节点的层数大于skiplist最大层数,那么直接在头节点对应层进行插入
    if (level > currentLevel) {
        for (int i = currentLevel; i < level; i++) {
            head.next[i] = node;
        }
        currentLevel = level;
    }
}5.search:查找元素
// 查找是否存在值为val的节点
public boolean search(int val) {
    Node temHead = head;
    for (int i = currentLevel - 1; i >= 0; i--) {
        temHead = getClosestNode(temHead, i, val);
        if (temHead.next[i] != null && temHead.next[i].val == val) {
            return  true;
        }
    }
    return false;
}6.deleteNode:删除节点
// 删除值为val的节点
public boolean deleteNode(int val) {
    boolean isDelete = false;
    Node temHead = head;
    // 逐层查找,逐层删除
    for (int i = currentLevel - 1; i >= 0; i--) {
        temHead = getClosestNode(temHead, i, val);
        if (temHead.next[i] != null && temHead.next[i].val == val) {
            // 链表删除节点
            temHead.next[i] = temHead.next[i].next[i];
            isDelete = true;
        }
    }
    return isDelete;
}测试
SkipList skipList = new SkipList();
skipList.insert(1);
skipList.insert(2);
skipList.insert(3);
System.out.println(skipList.search(0)); // 应为false:还未插入0
skipList.insert(4);
System.out.println(skipList.search(1)); // 应为true
System.out.println(skipList.deleteNode(0)); // 应为false:没有0,无法删除
System.out.println(skipList.deleteNode(1)); // 应为true:成功删除节点1
System.out.println(skipList.search(1)); // 应为false:节点1已被删除结果:
false
true
false
true
false完整代码
public class SkipList {
	// skiplist 头节点
	private Node head;
	// 当前跳表的最大层数
	private int currentLevel = 1;
	// skiplist的最大层数:可以满足2^64个元素的存储
	private static final int SKIPLIST_MAX_LEVEL = 32;
	// 节点是否进行层数增长的概率阈值
	private static final double SKIPLIST_P = 0.25;
	public SkipList() {
		this.head = new Node(-1, SKIPLIST_MAX_LEVEL);
	}
	// 插入节点值为val的新节点
	public void insert(int val) {
		int level = getRandomLevel();
		Node temHead = head;
		Node node = new Node(val, level); // 要插入的新节点
		for (int i = currentLevel - 1; i >= 0; i--) {
			temHead = getClosestNode(temHead, i, val);
			// 当前层数小于新建节点的层数
			if (i < level) {
				if (temHead.next[i] == null) {
					// 该节点是尾节点,直接将新节点插到最后
					temHead.next[i] = node;
				} else {
					// 否则将新节点插到前后两个节点中间
					node.next[i] = temHead.next[i];
					temHead.next[i] = node;
				}
			}
		}
		// 如果新建节点的层数大于skiplist最大层数,那么直接在头节点对应层进行插入
		if (level > currentLevel) {
			for (int i = currentLevel; i < level; i++) {
				head.next[i] = node;
			}
			currentLevel = level;
		}
	}
	// 查找是否存在值为val的节点
	public boolean search(int val) {
		Node temHead = head;
		for (int i = currentLevel - 1; i >= 0; i--) {
			temHead = getClosestNode(temHead, i, val);
			if (temHead.next[i] != null && temHead.next[i].val == val) {
				return  true;
			}
		}
		return false;
	}
	// 删除值为val的节点
	public boolean deleteNode(int val) {
		boolean isDelete = false;
		Node temHead = head;
		// 逐层查找,逐层删除
		for (int i = currentLevel - 1; i >= 0; i--) {
			temHead = getClosestNode(temHead, i, val);
			if (temHead.next[i] != null && temHead.next[i].val == val) {
				// 链表删除节点
				temHead.next[i] = temHead.next[i].next[i];
				isDelete = true;
			}
		}
		return isDelete;
	}
	// 在新建节点时,通过“摇骰子”的方式确定新建节点的层数
	private int getRandomLevel() {
		int level = 1;
		// 只要“摇骰子”的结果大于阈值,并且level小于最大层数,就一直增大当前节点的层数
		while (Math.random() > SKIPLIST_P && level < SKIPLIST_MAX_LEVEL) {
			level++;
		}
		return level;
	}
	// 找到在指定层idx中,于要查找值val最接近的节点
	private Node getClosestNode(Node node, int idx, int val) {
		while (node.next[idx] != null && val > node.next[idx].val) {
			node = node.next[idx];
		}
		return node;
	}
	class Node {
		int val; // 节点存储的值
		Node[] next; // 保存节点在对应层的下一个节点的指针
		public Node(int val, int level) {
			this.val = val;
			this.next = new Node[level]; // 表示当前节点有level层
		}
	}
}参考
		  本文作者: MerickBao 
		  本文链接: https://merickbao.top/post/%E7%AE%97%E6%B3%95/%E8%BF%9B%E9%98%B6%E7%AE%97%E6%B3%95/%E8%B7%B3%E8%A1%A8.html 
		  版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!