Skip to content

Commit 3013773

Browse files
committed
跳表:Redis中如何实现有序集合
步骤二:实现有索引的插入和删除
1 parent fa448ed commit 3013773

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
//不使用任何库函数,设计一个 跳表 。
2+
//
3+
// 跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思
4+
//想与链表相似。
5+
//
6+
// 例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 80、45 到跳表中,以下图的方式操作:
7+
//
8+
//
9+
//
10+
// 跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(
11+
//n)),空间复杂度是 O(n)。
12+
//
13+
// 了解更多 : https://oi-wiki.org/ds/skiplist/
14+
//
15+
// 在本题中,你的设计应该要包含这些函数:
16+
//
17+
//
18+
// bool search(int target) : 返回target是否存在于跳表中。
19+
// void add(int num): 插入一个元素到跳表。
20+
// bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。
21+
//
22+
//
23+
//
24+
// 注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。
25+
//
26+
//
27+
//
28+
// 示例 1:
29+
//
30+
//
31+
//输入
32+
//["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase",
33+
// "search"]
34+
//[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
35+
//输出
36+
//[null, null, null, null, false, null, true, false, true, false]
37+
//
38+
//解释
39+
//Skiplist skiplist = new Skiplist();
40+
//skiplist.add(1);
41+
//skiplist.add(2);
42+
//skiplist.add(3);
43+
//skiplist.search(0); // 返回 false
44+
//skiplist.add(4);
45+
//skiplist.search(1); // 返回 true
46+
//skiplist.erase(0); // 返回 false,0 不在跳表中
47+
//skiplist.erase(1); // 返回 true
48+
//skiplist.search(1); // 返回 false,1 已被擦除
49+
//
50+
//
51+
//
52+
//
53+
// 提示:
54+
//
55+
//
56+
// 0 <= num, target <= 2 * 10⁴
57+
// 调用search, add, erase操作次数不大于 5 * 10⁴
58+
//
59+
//
60+
// Related Topics 设计 链表 👍 278 👎 0
61+
62+
63+
package com.shuzijun.leetcode.editor.cn;
64+
65+
import java.util.Random;
66+
67+
/**
68+
* @author CoderDream
69+
*/
70+
public class Lc1206DesignSkiplist {
71+
72+
public static void main(String[] args) {
73+
Skiplist skiplist = new Lc1206DesignSkiplist().new Skiplist();
74+
}
75+
76+
//leetcode submit region begin(Prohibit modification and deletion)
77+
class Skiplist {
78+
final int HEAD_VALUE = -1; // 链表头节点的值
79+
final Node HEAD = new Node(HEAD_VALUE);
80+
81+
Node head; // 最左上角的头节点,所有操作在开始位置
82+
int levels; // 当前层级,即 head 节点所在的最高层数
83+
84+
public Skiplist() {
85+
head = HEAD;
86+
levels = 1;
87+
}
88+
89+
/**
90+
* <pre>
91+
* 从 head 开始,从左到右、从上到下依次查找
92+
* 1.小于,往右
93+
* 2.相同,则返回
94+
* 3.链表结尾,或大于,往下
95+
*
96+
* </pre>
97+
* @param target
98+
* @return
99+
*/
100+
public boolean search(int target) {
101+
Node n = head;
102+
while (n != null) {
103+
// 1.在同一层级向右查找,直到链表的结尾
104+
while (n.right != null && n.right.val < target) {
105+
n = n.right; // 向右
106+
}
107+
// 2.若找到,返回true
108+
Node right = n.right; // 要查找的节点
109+
if(right != null && right.val == target) {
110+
return true;
111+
}
112+
113+
// 3.若右侧数据较大,向下一层
114+
n = n.down; // 往下
115+
}
116+
117+
return false;
118+
}
119+
120+
/**
121+
* <pre>
122+
* 插入节点,将节点插入原链表中正确的排序位置
123+
* 1.定位插入位置:原链表中 >= num 的最小节点前
124+
* 2.插入新节点
125+
* 3.根据扔硬币决定(是否)生成索引
126+
* </pre>
127+
* @param num
128+
*/
129+
public void add(int num) {
130+
// 1.定位插入位置:原链表中 >= num 的最小节点前
131+
Node node = head;
132+
Node[] nodes = new Node[levels]; // 层级
133+
int i = 0; // 操作上述数组
134+
while (node != null) { // node == null, 到达原链表(第一层)
135+
while (node.right != null && node.right.val < num) {
136+
node = node.right; // 向右走
137+
}
138+
139+
nodes[i++] = node;
140+
141+
// 到达原链表(第一层)
142+
// if(node.down == null) {
143+
// break;
144+
// }
145+
146+
// 继续查找下一层的位置
147+
node = node.down;
148+
}
149+
150+
// 2.插入新节点
151+
node = nodes[--i]; // nodes 中最后一个元素
152+
153+
Node newNode = new Node(num, node.right, null);
154+
node.right = newNode;
155+
156+
// 3.根据扔硬币决定(是否)生成索引
157+
addIndicesByCoinFlip(newNode, nodes, i);
158+
}
159+
160+
/**
161+
* 抛硬币
162+
* @param target
163+
* @param nodes
164+
* @param indices 可以创建的层数
165+
*/
166+
private void addIndicesByCoinFlip(Node target, Node[] nodes, int indices) {
167+
Node downNode = target;
168+
// 1.抛硬币,在现有层级范围内建立索引
169+
Random random = new Random();
170+
int coins = random.nextInt(2); // 0 or 1, 50%概率
171+
while (coins == 1 && indices > 0) {
172+
Node prev = nodes[--indices]; // 数组的倒数第二个元素,level 2,(循环下一次为 level 3)
173+
Node newIndex = new Node(target.val, prev.right, downNode); // newIndex指向当前节点
174+
prev.right = newIndex; //
175+
//indices--;
176+
downNode = newIndex; // 保存新的向下节点
177+
coins = random.nextInt(2); // 0 or 1, 50%概率
178+
}
179+
// 2.抛硬币,决定是否建立一层超出跳表层数的索引层
180+
if(coins == 1) { // 新建一个索引层级
181+
// 新建索引节点和头节点
182+
Node newIndex = new Node(target.val, null, downNode); // 新层级,右边为null,下为上一次的down节点
183+
Node newHead = new Node(HEAD_VALUE, newIndex, head); // 头节点
184+
head = newHead; // head 指针上移
185+
levels++; // 跳表层数加 1
186+
}
187+
}
188+
189+
/**
190+
* <pre>
191+
* 遍历跳表,查找与给定值相同的节点,删除每一层
192+
* 1.获取该指定数据节点的前一个节点
193+
* 2.与当前层链表断开
194+
* 3.下移,删除每一层
195+
* </pre>
196+
* @param num
197+
* @return
198+
*/
199+
public boolean erase(int num) {
200+
boolean exist = false;
201+
Node n = head;
202+
while (n != null) {
203+
// 1.获取该指定数据节点的前一个节点
204+
while (n.right != null && n.right.val < num) {
205+
n = n.right; // 向右
206+
}
207+
// 2.与当前层链表断开
208+
Node right = n.right; // 要删除的节点
209+
if(right != null && right.val == num) {
210+
n.right = right.right; // 前一节点 指向待删除节点的后一节点
211+
right.right = null; // help GC
212+
exist = true;
213+
}
214+
215+
// 3.下移,删除每一层
216+
// 删除下一层
217+
n = n.down;
218+
}
219+
220+
return exist;
221+
}
222+
223+
class Node {
224+
int val;
225+
Node right, down;
226+
227+
Node(int val) {
228+
this(val, null, null);
229+
}
230+
231+
Node(int val, Node right, Node down) {
232+
this.val = val;
233+
this.right = right;
234+
this.down = down;
235+
}
236+
}
237+
}
238+
239+
/**
240+
* Your Skiplist object will be instantiated and called as such:
241+
* Skiplist obj = new Skiplist();
242+
* boolean param_1 = obj.search(target);
243+
* obj.add(num);
244+
* boolean param_3 = obj.erase(num);
245+
*/
246+
//leetcode submit region end(Prohibit modification and deletion)
247+
248+
249+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<p>不使用任何库函数,设计一个 <strong>跳表</strong> 。</p>
2+
3+
<p><strong>跳表</strong> 是在 <code>O(log(n))</code> 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。</p>
4+
5+
<p>例如,一个跳表包含 <code>[30, 40, 50, 60, 70, 90]</code> ,然后增加 <code>80</code>、<code>45</code> 到跳表中,以下图的方式操作:</p>
6+
7+
<p><img alt="" src="https://pic.leetcode.cn/1702370216-mKQcTt-1506_skiplist.gif" style="width: 500px; height: 173px;" /></p>
8+
9+
<p>跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 <code>O(n)</code>。跳表的每一个操作的平均时间复杂度是 <code>O(log(n))</code>,空间复杂度是 <code>O(n)</code>。</p>
10+
11+
<p>了解更多 :&nbsp;<a href="https://oi-wiki.org/ds/skiplist/" target="_blank">https://oi-wiki.org/ds/skiplist/</a></p>
12+
13+
<p>在本题中,你的设计应该要包含这些函数:</p>
14+
15+
<ul>
16+
<li><code>bool search(int target)</code> : 返回target是否存在于跳表中。</li>
17+
<li><code>void add(int num)</code>:&nbsp;插入一个元素到跳表。</li>
18+
<li><code>bool erase(int num)</code>: 在跳表中删除一个值,如果&nbsp;<code>num</code>&nbsp;不存在,直接返回false. 如果存在多个&nbsp;<code>num</code>&nbsp;,删除其中任意一个即可。</li>
19+
</ul>
20+
21+
<p>注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。</p>
22+
23+
<p>&nbsp;</p>
24+
25+
<p><strong>示例 1:</strong></p>
26+
27+
<pre>
28+
<b>输入</b>
29+
["Skiplist", "add", "add", "add", "search", "add", "search", "erase", "erase", "search"]
30+
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
31+
<strong>输出</strong>
32+
[null, null, null, null, false, null, true, false, true, false]
33+
34+
<strong>解释</strong>
35+
Skiplist skiplist = new Skiplist();
36+
skiplist.add(1);
37+
skiplist.add(2);
38+
skiplist.add(3);
39+
skiplist.search(0); // 返回 false
40+
skiplist.add(4);
41+
skiplist.search(1); // 返回 true
42+
skiplist.erase(0); // 返回 false,0 不在跳表中
43+
skiplist.erase(1); // 返回 true
44+
skiplist.search(1); // 返回 false,1 已被擦除
45+
</pre>
46+
47+
<p>&nbsp;</p>
48+
49+
<p><strong>提示:</strong></p>
50+
51+
<ul>
52+
<li><code>0 &lt;= num, target &lt;= 2 * 10<sup>4</sup></code></li>
53+
<li>调用<code>search</code>, <code>add</code>, &nbsp;<code>erase</code>操作次数不大于&nbsp;<code>5 * 10<sup>4</sup></code>&nbsp;</li>
54+
</ul>
55+
56+
<div><div>Related Topics</div><div><li>设计</li><li>链表</li></div></div><br><div><li>👍 278</li><li>👎 0</li></div>

0 commit comments

Comments
 (0)