跳表
本文最后更新于: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 协议 ,转载请注明出处!