LevelDb源碼之跳躍列表學習及C++11實現

跳躍列表是一種數據結構。他允許快速查詢一個有序連續元素的數據鏈表。平均查找和插入的事件複雜度爲O(log(n)),優於普通鏈表O(n)。快速查詢是通過維護一個多層次的鏈表,並且每一層鏈表中的元素是前一層鏈表元素的子集。

具體描述:
在這裏插入圖片描述

在levelDb中跳躍列表每個節點的數據結構如下所示:

   template <typename Key, class Comparator>
  struct SkipList<Key, Comparator>::Node {
    explicit Node(const Key& k) : key(k) {}

    Key const key;

    // Accessors/mutators for links.  Wrapped in methods so we can
    // add the appropriate barriers as necessary.
    Node* Next(int n) {
      assert(n >= 0);
      // Use an 'acquire load' so that we observe a fully initialized
      // version of the returned Node.
      return next_[n].load(std::memory_order_acquire);
    }

    void SetNext(int n, Node* x) {
      assert(n >= 0);
      // Use a 'release store' so that anybody who reads through this
      // pointer observes a fully initialized version of the inserted node.
      next_[n].store(x, std::memory_order_release);
    }

    // No-barrier variants that can be safely used in a few locations.
    Node* NoBarrier_Next(int n) {
      assert(n >= 0);
      return next_[n].load(std::memory_order_relaxed);
    }
    void NoBarrier_SetNext(int n, Node* x) {
      assert(n >= 0);
      next_[n].store(x, std::memory_order_relaxed);
    }

   private:
    // Array of length equal to the node height.  next_[0] is lowest level link.
    std::atomic<Node*> next_[1];
  };

可以看出 std::atomic<Node*> next_[1]成員就可以構建跳錶的層數。
在這裏插入圖片描述
其中層數是一個隨機數。

跳躍列表不像平衡樹等數據結構那樣提供對最壞情況的性能保證:由於用來建造跳躍列表採用隨機選取元素進入更高層
的方法,在小概率情況下會生成一個不平衡的跳躍列表(最壞情況例如最底層僅有一個元素進入了更高層,此時跳躍列
表的查找與普通列表一致)。但是在實際中它通常工作良好,隨機化平衡方案也比平衡二叉查找樹等數據結構中使用的
確定性平衡方案容易實現。跳躍列表在並行計算中也很有用:插入可以在跳躍列表不同的部分並行地進行,而不用對數
據結構進行全局的重新平衡。

實現細節:

因爲跳躍列表中的元素可以在多個列表中,所以每個元素可以有多於一個指針。跳躍列表的插入和刪除的實現與普通的鏈表操作類似,但高層元素必須在進行多個鏈表中進行插入或刪除。

跳躍列表的最壞時間性能具有一定隨機性,但是可以通過時間複雜度爲 O(n)的遍歷操作(例如在打印列表全部內容時)以無隨機的算法重整列表的結構,從而使跳躍列表的實際查找時間複雜度儘量符合理論平均值 O(log(n))。
插入操作:
先定義 記錄已經遍歷的節點的指針數組,開始找好合適的位置進行插入操作。即調用FindGreaterOrEqual。

 template <typename Key, class Comparator>
  void SkipList<Key, Comparator>::Insert(const Key& key) {
    // TODO(opt): We can use a barrier-free variant of FindGreaterOrEqual()
    // here since Insert() is externally synchronized.
    //記錄插入節點插入位置的前一個節點
    Node* prev[kMaxHeight];
    //找合適的插入位置
    Node* x = FindGreaterOrEqual(key, prev);
    // Our data structure does not allow duplicate insertion
    assert(x == nullptr || !Equal(key, x->key));

	//跟要插入節點確定一個層高
    int height = RandomHeight();
	//比當前最高層要高
    if (height > GetMaxHeight()) {
    	//將最高層記錄指向head_
      for (int i = GetMaxHeight(); i < height; i++) {
        prev[i] = head_;
      }
      // It is ok to mutate max_height_ without any synchronization
      // with concurrent readers.  A concurrent reader that observes
      // the new value of max_height_ will see either the old value of
      // new level pointers from head_ (nullptr), or a new value set in
      // the loop below.  In the former case the reader will
      // immediately drop to the next level since nullptr sorts after all
      // keys.  In the latter case the reader will use the new node.
      max_height_.store(height, std::memory_order_relaxed);
    }
    //創建新的節點,這裏使用內存池根據傳入的層高給新節點分配空間
    //並將相應的鍵值設置好
    x = NewNode(key, height);
    //相鏈表插入新的節點一樣插入,這裏從底層開始,指向下一個節點的對應層次上
    for (int i = 0; i < height; i++) {
      // NoBarrier_SetNext() suffices since we will add a barrier when
      // we publish a pointer to "x" in prev[i].
      x->NoBarrier_SetNext(i, prev[i]->NoBarrier_Next(i));
      prev[i]->SetNext(i, x);
    }
  }
  //在鏈表中找合適的插入點
  template <typename Key, class Comparator>
  typename SkipList<Key, Comparator>::Node*
  SkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key,
                                                Node** prev) const {
   //先記錄跳躍表的頭指針
    Node* x = head_;
    //獲取跳躍表的層數
    int level = GetMaxHeight() - 1;
   //從較高節點處開始,遍歷跳躍表
    while (true) {
      Node* next = x->Next(level);
      //將當前節點鍵值和插入的鍵值進行比較
      if (KeyIsAfterNode(key, next)) {
        // Keep searching in this list
        //不符合條件繼續找
        x = next;
      }
      //找到了
       else {
      //找到了,通過prev指針數組將節點記錄下來
        if (prev != nullptr) prev[level] = x;
        //將當前節點leve減爲0的時候將next返回
        if (level == 0) {
          return next;
        } else {
          // Switch to next list
          level--;
        }
      }
    }
  }

    template <typename Key, class Comparator>
  bool SkipList<Key, Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
    // null n is considered infinite
    //節點不爲空,將當前鍵值和插入鍵值進行比較
    return (n != nullptr) && (compare_(n->key, key) < 0);
  } 
 
 

自己實現的跳錶,使用智能指針,無需考慮內存釋放問題:

skipList.h

#pragma once
#include <vector>
#include <iostream>
#include <cassert>
#include <stdint.h>
#include <memory>
#define TYPE template <typename keyType, typename valType>
#define CLASS Node<keyType, valType>
#define SKIPCLASS skipList<keyType, valType> 

using namespace std ;
//使用leveldb中的隨機數生成器
class Random {
 private:
  uint32_t seed_;
 public:
  explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) {
    // Avoid bad seeds.
    if (seed_ == 0 || seed_ == 2147483647L) {
      seed_ = 1;
    }
  }
  uint32_t Next() {
    static const uint32_t M = 2147483647L;   // 2^31-1
    static const uint64_t A = 16807;  // bits 14, 8, 7, 5, 2, 1, 0
    uint64_t product = seed_ * A;
 
    seed_ = static_cast<uint32_t>((product >> 31) + (product & M));
    if (seed_ > M) {
      seed_ -= M;
    }
    return seed_;
  }
  uint32_t Uniform(int n) { return (Next() % n); }
 
  bool OneIn(int n) { return (Next() % n) == 0; }
  uint32_t Skewed(int max_log) {
    return Uniform(1 << Uniform(max_log + 1));
  }
};

TYPE
class Node {
public :
    Node() {}
    ~Node() {} 
public :
    keyType key ;
    valType value ;
    //後繼指針數組
    vector<shared_ptr<CLASS>>next ;
};

TYPE
class skipList {
public:
    skipList():level(1), head(nullptr), rnd(0xdeadbeef) {}
    ~skipList() {}
    shared_ptr<SKIPCLASS> createSkipList() ;
    shared_ptr<CLASS> createNode(int level, keyType key, valType val) ;
    bool insert(shared_ptr<SKIPCLASS>sl, keyType key, valType val) ;
    valType* search(shared_ptr<SKIPCLASS>sl, keyType key) ;
    bool erase(shared_ptr<SKIPCLASS>sl, keyType key) ;
    int getLevel() ;
public :
    int level ; //層數
    shared_ptr<CLASS>  head ;
    //最大層數
    Random rnd ;
};

//隨機產生當前跳錶的層數
TYPE 
int SKIPCLASS :: getLevel() {
   static const unsigned int kBranching = 4;
    int height = 1;
    while (height < level && ((rnd.Next() % kBranching) == 0)) {
      height++;
    }

    assert(height > 0);
    assert(height <= level);
    return height;
}

//創建一個新的跳錶節點
TYPE 
shared_ptr<CLASS> SKIPCLASS :: createNode(int level, keyType key, valType val) {
    //創建節點
    shared_ptr<CLASS> p = make_shared<CLASS>() ;
    //跳錶的層空間
    p->next.reserve(level) ;
    //申請層數
    //設置鍵值
    p->key = key ;
    p->value = val ;
    return p ;
}

//創建跳錶
TYPE
shared_ptr<SKIPCLASS> SKIPCLASS :: createSkipList() {
    //創建新的跳錶
    shared_ptr<SKIPCLASS> sl = make_shared<SKIPCLASS>() ;
    //預設跳錶層數爲0
    //創建跳錶節點
    int level = getLevel() ;
    //設置高度
    sl->level = level ;
    shared_ptr<CLASS> h = createNode(level, 0, 0) ;
    sl -> head = h ;
    //將head的next數組清空
    for(int i=0; i<level; i++) {
        h->next[i] = nullptr ;
    }
    srand(time(0)) ;
    return sl ;
}

//跳錶的插入操作
TYPE
bool SKIPCLASS :: insert(shared_ptr<SKIPCLASS>sl, keyType key, valType val) {
    cout << "插入元素:" << endl ;
    vector<shared_ptr<CLASS>>update ;
    update.reserve(level) ;
    shared_ptr<CLASS> q, p = sl->head ;
    
    //從最高層開始,進行搜索
    int i=sl->level-1; 
    for(;i>=0;i--) {
        //q->next[i]不爲空並且p->next[i]中的key小於插入的key
        while((q=p->next[i])&&q->key<key) {
            p=q ;
        }
        //找打了插入點的前一個節點保存
        update[i] = p ;
    }
    //key等於插入的key,只修改相應的值
    if(q && q->key == key) {
        q->value = val ;
        return true ;
    }
    int level = getLevel() ;
    //產生的隨機數比跳錶的層數大,則在update中將新添加的層指向header
    if(level > sl->level) {
        //擴增update
        update.reserve(level) ;
        //使得插入點的前一個節點保存頭結點,頭結點在跳躍表中的level應該是最高的
        for(int i=sl->level; i<level; i++) {
            update[i] = sl->head ;
        }   
        //設置跳錶的層數爲level
        sl->level = level ;
    }   
    //創建一個節點
    q = createNode(level, key, val) ;
    //新建一個待插入節點,前一個節點一層層插入
    for(int i=level-1; i>=0; --i) {
        q->next[i] = update[i]->next[i] ;
        update[i]->next[i] = q ;
    }

    cout << "插入元素成功~!" << endl ;
    return true ;
}

//調錶刪除節點操作
TYPE 
bool SKIPCLASS :: erase(shared_ptr<SKIPCLASS> sl, keyType key) {

    vector<shared_ptr<CLASS>> update ;
    shared_ptr<CLASS>q = nullptr, p = sl->head ;
    update.reserve(level) ;
    int i = sl->level -1 ;
    for(; i>=0; --i) {
        while((q = p->next[i]) && q->key < key) {
            p = q ;
        }
        update[i] = p ;
    }
    //判斷是否爲待刪除的鍵
    if(!q || (q&&q->key != key)) {
        return false ;
    }
    //逐層刪除
    for(i=sl->level-1; i>=0; --i) {
        if(update[i]->next[i] == q) {
            update[i]->next[i] = q->next[i] ;
        }
        if(sl->head->next[i] == nullptr) {
            sl->level -- ;
        }
    }
    q = nullptr ;
    return true ;
}

//跳錶的查找
TYPE
valType* SKIPCLASS :: search(shared_ptr<SKIPCLASS>sl, keyType key) {
    shared_ptr<CLASS> q, p = sl->head ;
    q = nullptr ;
    int i = sl->level-1 ;   
    for(; i>=0; --i) {
        while((q = p->next[i]) && q->key < key) {
            p = q ;
        }

        if(q && (key == q->key)) {
            return &q->value  ;
        }
    }
    return nullptr ;
}

test.cpp

#include "skipList.h"

int main() {
    shared_ptr<skipList<int, int>> sl = make_shared<skipList<int, int>>() ;
    //創建skiplist
    sl = sl->createSkipList() ;
    sl->insert(sl, 1, 1) ;
    sl->insert(sl, 2, 3) ;
    sl->insert(sl, 5, 3) ;
    sl->insert(sl, 7, 3) ;
    sl->insert(sl, 9, 3) ;
    sl->insert(sl, 10, 3) ;
    cout << "獲取鍵爲1的值:"<< *sl->search(sl, 1) << endl ;
    cout << "獲取鍵爲2的值:" << *sl->search(sl, 2) << endl ;
    cout << "刪除鍵爲:1" << "         刪除結果"<< sl->erase(sl, 1)  << endl ;
    cout << "找鍵爲1的值-------->" << endl ;
    if(sl->search(sl, 1) == nullptr) {
        cout << "沒找到節點" << endl ;
        return 1 ;
    } else 
    cout << "找到了節點!" << endl ;

}

運行結果:
在這裏插入圖片描述

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