手擼跳錶

原理參考: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. 插入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->
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章