原理參考:https://www.cnblogs.com/thrillerz/p/4505550.html
下面我來手擼一個跳錶,我已經上傳到github
跳錶的主要特徵
(1) 由很多層結構組成
(2) 每一層都是一個有序的鏈表
(3) 最底層(Level 1)的鏈表包含所有元素
(4) 如果一個元素出現在 Level i 的鏈表中,則它在 Level i 之下的鏈表也都會出現。
(5) 每個節點包含兩個指針,一個指向同一鏈表中的下一個元素,一個指向下面一層的元素。
跳錶結構 & 節點結構
跳錶包含一個top指針,指向最高Level的第一個節點。鏈表定義時,使用一個dumb節點來表示頭節點。我們這裏所有dumb節點是一個豎排,即所有層都以一個dumb頭節點開始
/*the biggest level*/
private int topLevel=0;
/*the start point of skip-list*/
private Node<T> top;
下面開始定義一個跳錶節點:
private static class Node<T extends Comparable>{
/* next node in the same level*/
Node<T> next;
/* next node in the next level*/
Node<T> down;
T t;
int level;
}
一個跳錶節點包含其節點的level,同level的下一個節點和下一個level節點(包含的值T和本節點相同)。
節點的層級是創建時使用隨機數決定的:
/**random level**/
private static int getLevel(){
int tempLevel=1;
while ((int)Math.round(Math.random())==1){
tempLevel++;
}
return tempLevel;
}
如何插入一個節點?
可以發現就是一層一層去找節點的前驅節點。還有一個特點,比如一個節點的level爲3,它只需要1-3層的前驅節點。
下面是找前驅節點的代碼:
// find the predecessor node
Node[] predecessors=new Node[temp.level];
Node<T> current=top;
while (true){
// 判斷當前節點是不是本層最後一個節點
// reach the tail of current level
if (current.next==null){
// set predecessor node if current node is not higher than inserted node
if (current.level<=temp.level) predecessors[current.level-1]=current;
// if reach the lowest level,then exit
if (current.down==null){
break;
}else {
// if not the lowest level,then go to the next lower level
current=current.down;
}
}else {
// iterate through the current level,util reach the tail or find the predecessor
while (current.next!=null){
// p.next < current
if (current.next.t.compareTo(t)<0){
current=current.next;
}else {
break;
}
}
// reach the tail or get the value bigger than inserted,current is the predecessor of this level
if (current.level<=temp.level) predecessors[current.level-1]=current;
if (current.down!=null){
current=current.down;
}else {
break;
}
}
}
分成了兩種情況,一種是本層只有一個dumb節點,另一個是有大於一個節點。找到這些前驅節點放到一個數組裏面。下面的操作和鏈表很像:
// all predecessors are calculated,insert the node in each level,pre hold the node in the upper next level
Node<T> pre=null;
for (int i = predecessors.length-1; i >=0; i--) {
Node<T> a=new Node<>(predecessors[i].level,temp.t);
//下面兩行就是簡單的單鏈表操作
a.next=predecessors[i].next;
predecessors[i].next=a;
//將上層節點的down指向本身,第一層的pre爲null
if (pre!=null) pre.down=a;
pre=a;
}
跳錶如何查找?
前面說明了如何插入,如何尋找前驅節點?就是逐層下降,找到每層第一個大於被插入節點的節點的前一個節點。
查找也是差不多一個道理,不過區別在於每層找到第一個等於查找元素的就返回。
public Optional<T> search(T t){
T result=null;
//search from top
Node<T> current=top;
while (true){
//reach the tail of current level
if (current.next==null){
// if not the lowest
if (current.down!=null){
current=current.down;
}else {
break;
}
}else{
// search the current level,either reach the tail or find the predecessor
while (current.next!=null){
if (current.next.t!=null && current.next.t.compareTo(t)==0){
result=current.next.t;
//找到第一個就返回!!!!
return Optional.ofNullable(t);
}
// if current node is less than arg
if (current.next.t==null || current.next.t.compareTo(t)<0){
current=current.next;
}else {
break;
}
}
// current.next==null or current is the predecessor in the current level
if (current.down!=null){
current=current.down;
}else {
break;
}
}
}
return Optional.ofNullable(result);
}
跳錶如何刪除?
插入的時候,我們學會找到了前驅節點,刪除就是找到全部前驅節點,然後讓前驅節點指向被刪節點的下一個(可能爲null)
public boolean remove(T t){
Optional<Node<T>[]> p=findPredecessors(t);
if (p.isPresent()){
Node<T>[] predecessors=p.get();
Arrays.asList(predecessors).forEach(s->{
s.next=s.next.next;
});
return true;
}else {
return false;
}
}
概念測試
- 插入1–10,插入一個重複元素5,然後刪除5:
public static void main(String[] args) {
SkipList<Integer> skipList=new SkipList<>();
for (int i = 0; i < 10; i++) {
/*if (Math.round(Math.random())==1) {
skipList.insert(i);
}*/
skipList.insert(i);
}
skipList.insert(5);
skipList.printSkipList();
System.out.println();
System.out.println("-----------");
boolean s=skipList.remove(5);
System.out.println(s);
System.out.println();
// 打印跳錶的實用類
skipList.printSkipList();
}
console:
null->4->
null->1->4->
null->0->1->4->5->5->6->
null->0->1->2->3->4->5->5->6->7->8->9->
-----------
true
null->4->
null->1->4->
null->0->1->4->5->6->
null->0->1->2->3->4->5->6->7->8->9->