今天来挑战一道超有意思的 LeetCode 题目 ——1206. 设计跳表。这道题可不简单,但是别怕,有我在,带你一步步吃透它💪
相关文档
什么是跳表
跳表(Skip List)是一种在 (O(log(n))) 时间内完成增加、删除、搜索操作的数据结构。它就像是链表的超级进化版👾 相比于树堆与红黑树,功能和性能都差不多,关键是代码还更短,主打一个短小精悍!它的设计思想和链表很相似,但是却有着链表没有的高效。
代码实现
直接上代码,看看我是怎么实现这个跳表的。
class Skiplist {
static int MAX_LEVEL = 32;
Node head;
int level;
double p = 0.25;
Random r = new Random();
public Skiplist() {
this.head = new Node(-1, MAX_LEVEL);
this.level = 0;
}
public boolean search(int target) {
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < target) {
curr = curr.forward[i];
}
}
return curr.forward[0]!= null && curr.forward[0].val == target;
}
public void add(int num) {
Node[] update = new Node[MAX_LEVEL];
Arrays.fill(update, this.head);
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < num) {
curr = curr.forward[i];
}
update[i] = curr;
}
int lvl = randomLvl();
this.level = Math.max(this.level, lvl);
Node newNode = new Node(num, lvl);
for (int i = lvl - 1; i >= 0; i--) {
newNode.forward[i] = update[i].forward[i];
update[i].forward[i] = newNode;
}
}
public boolean erase(int num) {
Node[] update = new Node[MAX_LEVEL];
Arrays.fill(update, this.head);
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < num) {
curr = curr.forward[i];
}
update[i] = curr;
}
if (curr.forward[0] == null || curr.forward[0].val!= num) {
return false;
}
Node target = curr.forward[0];
for (int i = 0; i < MAX_LEVEL; i++) {
if (update[i].forward[i] == null || update[i].forward[i].val!= target.val) {
continue;
}
update[i].forward[i] = update[i].forward[i].forward[i];
}
while (this.level > 1 && this.head.forward[this.level - 1] == null) {
this.level--;
}
return true;
}
private int randomLvl() {
int lvl = 1;
while (r.nextDouble() <= p && lvl < MAX_LEVEL) {
lvl++;
}
return lvl;
}
static class Node {
int val;
Node[] forward;
Node(int val, int level) {
this.val = val;
this.forward = new Node[level];
}
}
}
代码思路解析
初始化部分
static int MAX_LEVEL = 32;
Node head;
int level;
double p = 0.25;
Random r = new Random();
public Skiplist() {
this.head = new Node(-1, MAX_LEVEL);
this.level = 0;
}
这里定义了一些常量和变量。MAX_LEVEL 表示跳表的最大层数,head 是跳表的头节点,level 表示当前跳表的实际层数,p 是一个概率值,用于决定新节点的层数,r 是一个随机数生成器。在构造函数中,初始化了头节点,值为 -1,层数为 MAX_LEVEL,当前实际层数为 0。这就像是搭房子,先把地基(头节点)打好🧱
搜索方法 search(int target)
public boolean search(int target) {
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < target) {
curr = curr.forward[i];
}
}
return curr.forward[0]!= null && curr.forward[0].val == target;
}
从最高层开始,一层一层向下搜索。在每一层,只要当前节点的下一个节点存在且值小于目标值,就继续向前移动。这就像是在多层的高速公路上找出口,先在最上面的快车道找,如果没找到就去下一层慢车道找🚗 最后判断最底层的下一个节点是否是目标节点。
添加方法 add(int num)
public void add(int num) {
Node[] update = new Node[MAX_LEVEL];
Arrays.fill(update, this.head);
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < num) {
curr = curr.forward[i];
}
update[i] = curr;
}
int lvl = randomLvl();
this.level = Math.max(this.level, lvl);
Node newNode = new Node(num, lvl);
for (int i = lvl - 1; i >= 0; i--) {
newNode.forward[i] = update[i].forward[i];
update[i].forward[i] = newNode;
}
}
首先,和搜索一样,从最高层开始找到每一层应该插入的位置,并记录下来(用 update 数组)。然后,通过 randomLvl 方法随机生成新节点的层数。接着,创建新节点,再从新节点的最高层开始,将新节点插入到记录的位置中。这就像是在多层书架上插书,先找到每层应该插的位置,然后把新书插进去📚
删除方法 erase(int num)
public boolean erase(int num) {
Node[] update = new Node[MAX_LEVEL];
Arrays.fill(update, this.head);
Node curr = this.head;
for (int i = level - 1; i >= 0; i--) {
while (curr.forward[i]!= null && curr.forward[i].val < num) {
curr = curr.forward[i];
}
update[i] = curr;
}
if (curr.forward[0] == null || curr.forward[0].val!= num) {
return false;
}
Node target = curr.forward[0];
for (int i = 0; i < MAX_LEVEL; i++) {
if (update[i].forward[i] == null || update[i].forward[i].val!= target.val) {
continue;
}
update[i].forward[i] = update[i].forward[i].forward[i];
}
while (this.level > 1 && this.head.forward[this.level - 1] == null) {
this.level--;
}
return true;
}
还是先从最高层开始找到每一层要删除节点的前一个节点(用 update 数组记录)。如果最底层的下一个节点不是要删除的节点,就返回 false。然后,从最低层开始,将所有指向要删除节点的指针跳过该节点。最后,如果删除节点后,最高层没有节点了,就降低跳表的实际层数。这就像是从多层停车场里开走一辆车,先找到每层对应的停车位,然后把车开走,再看看停车场最高层是不是空了,如果空了就把最高层拆掉🚗
随机层数方法 randomLvl()
private int randomLvl() {
int lvl = 1;
while (r.nextDouble() <= p && lvl < MAX_LEVEL) {
lvl++;
}
return lvl;
}
这个方法用于随机生成新节点的层数。每次生成一个 0 到 1 之间的随机数,如果小于概率 p 且当前层数小于 MAX_LEVEL,就增加层数。这就像是抽奖,每次都有一定概率(p)抽到升级卡,抽到了就升级,直到抽不到或者达到最大等级🎰
总结
通过这道题,我们深入了解了跳表这种神奇的数据结构,并且亲手实现了它。虽然过程有点烧脑,但是掌握之后是不是感觉自己又变强了呢😎 希望这篇文章能帮助大家更好地理解跳表,一起在算法的世界里乘风破浪!如果有任何问题,欢迎在评论区留言哦~