跳躍表原理與Java實現

跳躍表總結

解決了有序鏈表結構查找特定值困難的問題,查找特定值的時間複雜度爲O(logn),他是一種可以代替平衡樹的數據結構

問題

假如我們要用某種數據結構來維護一組有序的int型數據的集合,並且希望這個數據結構在插入、刪除、查找等操作上能夠儘可能着快速,那麼,你會用什麼樣的數據結構呢?
數組:
採用二分法可以在0(logn)的時間裏查找指定元素,但是插入和刪除不友好:查找0(logn),插入和刪除0(n)
鏈表:
查找0(n),但是插入和刪除就是0(1)

優化思路

在這裏插入圖片描述
如果我們現在查找元素爲9的節點,需要從頭節點遍歷8次。
我們增加一些路徑加快查找速度
在這裏插入圖片描述
現在我們遍歷5次就查找到了。
在這裏插入圖片描述
基於這種方法,對於具有n個元素的鏈表,我們採用logN+1層指針路徑的方法,可以實現在0(logn)查找到元素

調錶的搜索

在這裏插入圖片描述
例子:查找元素 117

(1) 比較 21, 比 21 大,往後面找
(2) 比較 37, 比 37大,比鏈表最大值小,從 37 的下面一層開始找
(3) 比較 71, 比 71 大,比鏈表最大值小,從 71 的下面一層開始找
(4) 比較 85, 比 85 大,從後面找
(5) 比較 117, 等於 117, 找到了節點。

我靠,java程序員表示真難懂!!!

//如果存在x,返回x所在的節點。否則返回x的後繼節點
find(x){
   p=top;   //p指向頭節點的最高層節點
   while(1){ //不斷循環
      while(p->next->key<x) //橫向查找滿足key>=x的p
         p=p->next; 
      if(p-dowm==null){ //如果
         return p-next;
      }
      p=p->down;
   }
}

在這裏插入圖片描述
滿足redis標準的跳躍表數據結構

public class SkipList{
    Node header,tail;
    int level; //層數最大的節點
    int length; //目前節點的個數
    static class level{
         Node next; //請進指針
         int span; //跨度
    }
    static class Node implements Comparable { //帶有不同層高的節點
         Object data=-1;
         level[] levels; //存儲此節點鎖數的層數。每次創建時都會隨機生成一個32內隨機大小的level數組
         double score; //按各個節點的分支大小從小到大排列
         Node pre;
         public Node(value,level){
             this.value=value;
             this.level=leve;
         }
         @Override
         public int compareTo(Object o) {
              return this.value > ((Node )o).value ? 1 : -1;
         }
    }
}

調錶的插入

先確定元素佔據的層數k(採用丟硬幣的方式)
然後在level數組中插入相關的元素

int random_level()  
{  
    K = 1;  
  
    while (random(0,1))   //包括0,不包括1
        K++;  
  
    return K;  
}  

跳躍表的性質

  1. 跳躍表由很多層組成,每一層都是一個有序列表
  2. 跳躍表最底層鏈表包含所有元素
  3. 如果一個元素出現在 Level i 的鏈表中,則它在 Level i 之下的鏈表也都會出現。
  4. 跳躍表是一種隨機化數據結構,通過拋硬幣決定層數

對比

  1. 對比二叉查找樹
    因爲查找查找樹的插入、刪除、查找也是近似 O(logn) 的時間複雜度。
    不過,二叉查找樹是有可能出現一種極端的情況的,就是如果插入的數據剛好一直有序,那麼所有節點會偏向某一邊。
  2. 紅黑樹
    紅黑樹可以說是二叉查找樹的一種變形,紅黑在查找,插入,刪除也是近似O(logn)的時間複雜度

而且紅黑樹插入,刪除結點時,是通過調整結構來保持紅黑樹的平衡,比起跳躍表直接通過一個隨機數來決定跨越幾層,在時間複雜度的花銷上是要高於跳躍表的

自定義簡單跳躍表的實現-區別於redis的有序列表

自定義數據結構

public class SkipList{
    Node header=new Node(-1,16); //跳躍表的頭節點
    int levelMax=16; //允許最大層數
    int length; //當前跳錶節點的個數
    int levelCount=1; //當前跳躍表的層數,初始化爲1
    static class level{ //層節點,相當於hashmap的entry
        Node next;  //指向下一個節點
        int span;  //跨越的層數,也就是levels數組的大小
        public level(){};
        public level(int span){
           this.span=span;
           this.next=new Node(span);
        }
    }
    static class Node implements Comparable { //帶有不同層高的節點
         int value=-1; //數據初始化爲-1,爲了簡便爲int類型
         level[] levels; //存儲此節點的層節點。每次創建時都會隨機生成一個32內隨機大小的level數組,相當於hashmap的鏈表
         public Node(value,span){
             this.value=value;
             this.span=span;
             this.levels=new level(span);
         }
         @Override
         public int compareTo(Object o) {
              return this.value > ((Node )o).value ? 1 : -1;
         }
    }

跳躍表查詢方法

待優化,找到值還要一致向下遍歷,在levels[i]層找到,則在其下面各層肯定存在

 public Node find(int value){
         Node temp=header;
         for(int levelCount-1;i>=0;i--){ //從最高層遍歷到最底層-縱向遍歷
              while(temp.levels[i].next!=null && temp.levels[i].next.value<value){ //橫向遍歷,直到值不小於查找值   temp最終存放指定值的前驅
                   temp=temp.levels[i].next;
              }
         }
         //遍歷所有層後,判斷是否找到              待定:應該上移判斷
         if(temp.levels[i].next!=null && temp.levels[i].next.value==value){
             System. out.println( value+ " 查找成功");
             return temp.levels[i].next.value;
         }
         return null;
    }

向跳躍表插入值,不允許重複

 public void insert(int value){
        int level =getLevel();
        Node newNode = newNode(value, level); //創建一個新節點
        Node[] update= newNode[level]; //記錄每一層插入位置的前驅節點
        Node temp = head;
        for(int i = level - 1; i >= 0; i--) { //找到每一層插入的前驅節點
            while(temp.levels[i].next != null&& temp.levels[i].next.value< value) {
                temp = temp.levels[i].next;
            }
            update[i] = temp;
        }
        //把插入節點的每一層連接起來
        for( int i = 0; i < level; i++) {
           newNode.levels[i].next = update[i].levels[i].next;
           update[i].levels[i].next = newNode;
        }
        //判斷是否需要更新跳躍表的層數
       if(level > levelCount) {
           levelCount = level;
       } 
       size++;
       System. out.println( value+ " 插入成功");
       }
    }
}

在跳躍表刪除值

假設刪除值一定存在

 public void delete(int value){
     int level; //存儲刪除節點得層數
     Node temp = head;
     Node[] update= newNode[levelCount]; //此處大小設置爲levelCount!!
     //1. 首先找到刪除值所有層得前驅位置
     for(int i = levelCount- 1; i >= 0; i--) { //找到每一層插入的前驅節點
          while(temp.levels[i].next != null&& temp.levels[i].next. value< value) {
              temp = temp.levels[i].next;
          }
          update[i] = temp;
     }
     //2. 更新跳躍表得節點數
     if(temp.levels[i].next != null&& temp.levels[i].next. value== value) {
        size--;
     }
     System. out.println( value+ " 刪除成功");
     //刪除節點後,連接鏈表
     for(int i=0;i< levelCount;i++) {
        if(update[i].levels[i].next != null&& update[i].levels[i].next.value== value) {
            update[i].levels[i].next = update[i].levels[i].next.levels[i].next;
        }
     }

模擬拋硬幣

自己理解,和其他人優點區別

int getLevel() {
	int level = (int)(Math.random() * (this.levelMax+1)); //隨機產生[0,1)的數字
	System.out.println( "當前的level = "+ level);
	return level;
}

隨機生成1-16的整數

方法1:使用Random類

Random r=new Random();
int a=r.nextInt(16)+1;  //r.nextInt(16)會生成0-15

方法2:使用Math.random()

int num = (int) (Math.random() * 16 + 1); //Math.random()隨機生成[0,1)*16=[1,17)  int[1,17)=[1,16]

總結

學習數據結構,不能只學習原理,一定要手動實現一遍
待更新。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章