-
昨天leetcode每日一题是跳表,之前学redis就没去写跳表,这次就逃不过了。
-
这里使用了len数组,来表示每个数字之间的间隔,方便复杂的查询功能。
-
主要问题有
- 为什么len数组记录的是数字之间的间隔,不是每一层从头到尾的次序?
如果不用间隔,增删的时候会很麻烦,会改变后面的数据 - MAXL为什么是20?
这个取决于数据量,是2的几次方,是几次方就填多少。 - 为什么决定数据有几层要随机?
这里用的是java的random函数,数字位于[0, 1),这个时候对半分,小于0.5就加,大于等于就不加,这样就类似于抛硬币,概率为二分之一,这个时候可以类似得到完全二叉树的结构,使得得到log(N)的时间复杂度。
- 下面附上代码
java">class Skiplist {
// 跳跃最大层数限制
public static int MAXL = 20;
public static int MAXN = 100001;
// 空间使用计数
public static int cnt;
// 节点的key
public static int[] key = new int[MAXN];
// 节点key的计数
public static int[] count = new int[MAXN];
// 节点拥有多少层指针
public static int[] level = new int[MAXN];
// 节点每一层指针指向哪个节点
public static int[][] next = new int[MAXN][MAXL + 1];
// 节点每一层指针的长度(底层跨国多少数、左开右闭)
public static int[][] len = new int[MAXN][MAXL + 1];
public Skiplist() {
cnt = 2; // 头节点是1,新节点从2开始
key[1] = Integer.MIN_VALUE;
level[1] = MAXL;
// 初始化头节点的next和len数组
for (int i = 1; i <= MAXL; i++) {
next[1][i] = 0;
len[1][i] = 0;
}
}
// 从i号节点的h层,返回key为num的节点,空间编号为多少
public static int find(int i, int h, int num) {
// 非底层,逐层向下查找
while (next[i][h] != 0 && key[next[i][h]] < num) {
i = next[i][h];
}
if (h == 1) {
// 在底层,检查下一个节点是否是目标值
if (next[i][h] != 0 && key[next[i][h]] == num) {
return next[i][h];
}
return 0; // 没找到返回0
}
// 递归到下一层继续查找
return find(i, h - 1, num);
}
// 扔骰子决定节点的层数
public static int random() {
int ans = 1;
while (Math.random() < 0.5) {
ans++;
}
return Math.min(ans, MAXL);
}
// 返回target是否存在于跳表中。
public boolean search(int t) {
return find(1, MAXL, t) != 0; // 判断是否找到
}
// 插入一个元素到跳表
public void add(int t) {
int j = find(1, MAXL, t);
if (j != 0) {
addCount(1, MAXL, t);
} else {
int newNode = cnt; // 当前cnt作为新节点编号
key[newNode] = t;
count[newNode] = 1;
level[newNode] = random();
addNode(1, MAXL, newNode);
cnt++; // 递增cnt
}
}
public boolean erase(int t) {
int j = find(1, MAXL, t);
if (j != 0) {
if (count[j] > 1) {
removeCount(1, MAXL, t);
} else {
removeNode(1, MAXL, j);
}
return true;
}
return false;
}
// 加词频
public static void addCount(int i, int h, int num) {
while (next[i][h] != 0 && key[next[i][h]] < num) {
i = next[i][h];
}
if (h == 1) {
count[next[i][h]]++;
} else {
addCount(i, h - 1, num);
}
len[i][h]++;
}
// 当前i号节点的h层,插入空间编号为j的节点
public static int addNode(int i, int h, int j) {
int rightCnt = 0;
while (next[i][h] != 0 && key[next[i][h]] < key[j]) {
rightCnt += len[i][h];
i = next[i][h];
}
if (h == 1) {
next[j][h] = next[i][h];
next[i][h] = j;
len[j][h] = len[i][h];
len[i][h] = 1;
return rightCnt + 1;
} else {
int downCnt = addNode(i, h - 1, j);
if (h > level[j]) {
len[i][h]++; // 当前层高于新节点的最大层数
} else {
next[j][h] = next[i][h];
next[i][h] = j;
len[j][h] = len[i][h] - downCnt;
len[i][h] = downCnt + 1;
}
return rightCnt + downCnt;
}
}
public static void removeCount(int i, int h, int num) {
while (next[i][h] != 0 && key[next[i][h]] < num) {
i = next[i][h];
}
if (h == 1) {
count[next[i][h]]--;
} else {
removeCount(i, h - 1, num);
}
len[i][h]--;
}
public static void removeNode(int i, int h, int j) {
if (h < 1) {
return;
}
while (next[i][h] != 0 && key[next[i][h]] < key[j]) {
i = next[i][h];
}
if (h > level[j]) {
len[i][h]--;
} else {
next[i][h] = next[j][h];
len[i][h] += len[j][h] - 1;
}
removeNode(i, h - 1, j);
}
// 获取元素出现次数
public int getCount(int num) {
int j = find(1, MAXL, num);
return j != 0 ? count[j] : 0;
}
// 找到小于num的最大元素(前驱)
public Integer lower(int num) {
int i = 1;
int h = MAXL;
int res = Integer.MIN_VALUE;
while (h >= 1) {
while (next[i][h] != 0 && key[next[i][h]] < num) {
i = next[i][h];
res = key[i]; // 更新候选值
}
h--;
}
return res != Integer.MIN_VALUE ? res : null;
}
// 找到大于num的最小元素(后继)
public Integer higher(int num) {
int i = 1;
int h = MAXL;
while (h >= 1) {
while (next[i][h] != 0 && key[next[i][h]] <= num) {
i = next[i][h];
}
h--;
}
return next[i][1] != 0 ? key[next[i][1]] : null;
}
// 范围查询 [left, right]
public List<Integer> range(int left, int right) {
List<Integer> result = new ArrayList<>();
int i = findLowerBound(1, MAXL, left);
while (i != 0 && key[i] <= right) {
for (int c = 0; c < count[i]; c++) {
result.add(key[i]);
}
i = next[i][1];
}
return result;
}
// 辅助方法:找到第一个>=left的节点
private int findLowerBound(int i, int h, int left) {
if (h == 0) return 0;
while (next[i][h] != 0 && key[next[i][h]] < left) {
i = next[i][h];
}
int candidate = findLowerBound(i, h-1, left);
return candidate != 0 ? candidate : next[i][h];
}
}