日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
數據結構:跳躍鏈表

本文轉載自微信公眾號「潛行前行」,作者cscw。轉載本文請聯(lián)系潛行前行公眾號。

專注于為中小企業(yè)提供成都網站設計、做網站服務,電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)銀州免費做網站提供優(yōu)質的服務。我們立足成都,凝聚了一批互聯(lián)網行業(yè)人才,有力地推動了上千余家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網站建設實現規(guī)模擴充和轉變。

什么是跳躍鏈表

開發(fā)時經常使用的平衡數據結構有B數、紅黑數,AVL數。但是如果讓你實現其中一種,很難,實現起來費時間。而跳躍鏈表一種基于鏈表數組實現的快速查找數據結構,目前開源軟件 Redis 和 LevelDB 都有用到它。它的效率和紅黑樹以及 AVL 樹不相上下

跳躍鏈表結構

結構

 
 
 
 
  1. public class SkipList {
  2.     //跳躍表的頭尾
  3.     private SkipListNode head;
  4.     //跳躍表含的元素長度
  5.     private int length;
  6.     //跳表的層數 的歷史最大層數
  7.     public int maxLevel;
  8.     public SecureRandom random;
  9.     private static final int MAX_LEVEL = 31;
  10.     public SkipList() {
  11.         //初始化頭尾節(jié)點及兩者的關系
  12.         head = new SkipListNode<>(SkipListNode.HEAD_SCORE, null, MAX_LEVEL);
  13.         //初始化大小,層,隨機
  14.         length = 0;
  15.         maxLevel = 0; // 層數從零開始計算
  16.         random = new SecureRandom();
  17.     }
  18.     ...
  • header:指向跳躍表的頭節(jié)點
  • maxLevel:記錄目前跳躍表,層數最大節(jié)點的層數
  • length:鏈表存在的元素長度

節(jié)點

跳躍鏈表節(jié)點的組成:前節(jié)點、后節(jié)點、分值(map的key值)、及存儲對象 value

 
 
 
 
  1. public class SkipListNode {
  2.     //在跳表中排序的 分數值
  3.     public double score;
  4.     public T value;
  5.     public int level;
  6.     // 前后節(jié)點
  7.     public SkipListNode next,pre;
  8.     //上下節(jié)點形成的層
  9.     public SkipListNode[] levelNode;
  10.     private SkipListNode(double score, int level){
  11.         this.score = score;
  12.         this.level = level;
  13.     }
  14.     public SkipListNode(double score, T value, int level) {
  15.         this.score = score;
  16.         this.value = value;
  17.         this.level = level;
  18.         this.levelNode = new SkipListNode[level+1];
  19.         //初始化 SkipListNode 及 每一層的 node
  20.         for (int i = level; i > 0; --i) {
  21.             levelNode[i] = new SkipListNode(score, level);
  22.             levelNode[i].levelNode = levelNode;
  23.         }
  24.         this.levelNode[0] = this;
  25.     }
  26.     @Override
  27.     public String toString() {  return "Node[score=" + score + ", value=" + value + "]"; }
  28. }

跳表是用空間來換時間

在我實現的跳躍鏈表節(jié)點,包括一個 levelNode 成員屬性。它就是節(jié)點層。跳躍鏈表能實現快速訪問的關鍵點就是它

平時訪問一個數組,我們是順序遍歷的,而跳躍鏈表效率比數組鏈表高,是因為它使用節(jié)點層存儲多級索引,形成一個稀疏索引,所以需要的更多的內存空間

跳躍鏈表有多快

如果一個鏈表有 n 個結點,每兩個結點抽取出一個結點建立索引的話,那么第一層索引的結點數大約就是 n/2,第二層索引的結點數大約為 n/4,以此類推第 m 層索引的節(jié)點數大約為 n/(2^m)

訪問數據時可以從 m 層索引查詢定位到 m-1 層索引數據。而 m-1 大約是 m 層的1/2。也就是說最優(yōu)的時間復雜度為O(log/N)

最差情況。在實際實現中,每一層索引是無法每次以數據數量對折一次實現一層索引。因此折中的方式,每一層的索引是隨機用全量數據建一條。也就是說最差情況時間復雜度為O(N),但最優(yōu)時間復雜度不變

查詢

查詢一開始是遍歷最高層 maxLevel 的索引 m。按照以下步驟查詢出等于 score 或者最接近 score 的左節(jié)點

1:如果同層索引的 next 節(jié)點分值小于查詢分值,則跳到 next 節(jié)點。cur = next

2:如果 next 為空?;蛘遪ext節(jié)點分值大于查詢分值。則跳到下一層 m-1 索引,循環(huán) 2

循環(huán) 1、2 步驟直到訪問到節(jié)點分值和查詢分值一致,或者索引層為零

 
 
 
 
  1. // SkipList
  2.     private SkipListNode findNearestNode(double score) {
  3.         int curLevel = maxLevel;
  4.         SkipListNode cur = head.levelNode[curLevel];
  5.         SkipListNode next = cur.next;
  6.         // 和當前節(jié)點分數相同 或者 next 為 null
  7.         while (score != cur.score && curLevel > 0) {
  8.             // 1 向右 next 遍歷
  9.             if (next != null && score >= next.levelNode[0].score) {
  10.                 cur = next;
  11.             }
  12.             next = cur.levelNode[curLevel].next;
  13.             // 2 向下遍歷,層數減1
  14.             while ((next == null || score < next.levelNode[0].score) && curLevel > 0) {
  15.                 next = cur.levelNode[--curLevel].next;
  16.             }
  17.         }
  18.         // 最底層的 node。
  19.         return cur.levelNode[0];
  20.     }
  21.     public SkipListNode get(double score) {
  22.         //返回跳表最底層中,最接近這個 score 的node
  23.         SkipListNode p = findNearestNode(score);
  24.         //score 相同,返回這個node
  25.         return p.score == score ? p : null;
  26.     }

插入

如果分值存在則替換 value

如果分值對應節(jié)點不存在,則隨機一個索引層數 level (取值 0~31)。然后依靠節(jié)點屬性 levelNode 加入 0 到 level 層的索引

 
 
 
 
  1. //SkipList
  2.     public T put(double score, T value) {
  3.         //首先得到跳表最底層中,最接近這個key的node
  4.         SkipListNode p = findNearestNode(score);
  5.         if (p.score == score) {
  6.             // 在跳表中,只有最底層的node才有真正的value,只需修改最底層的value就行
  7.             T old = p.value;
  8.             p.value = value;
  9.             return old;
  10.         }
  11.         // nowNode 為新建的最底層的node。索引層數 0 到 31
  12.         int nodeLevel = (int) Math.round(random.nextDouble() * 32);
  13.         SkipListNode nowNode = new SkipListNode(score, value, nodeLevel);
  14.         //初始化每一層,并連接每一層前后節(jié)點
  15.         int level = 0;
  16.         while (nodeLevel >= p.level) {
  17.             for (; level <= p.level; level++) {
  18.                 insertNodeHorizontally(p.levelNode[level], nowNode.levelNode[level]);
  19.             }
  20.             p = p.pre;
  21.         }
  22.         // 此時 p 的層數大于 nowNode 的層數才進入循環(huán)
  23.         for (; level <= nodeLevel; level++) {
  24.             insertNodeHorizontally(p.levelNode[level], nowNode.levelNode[level]);
  25.         }
  26.         this.length ++ ;
  27.         if (this.maxLevel < nodeLevel) {
  28.             maxLevel = nodeLevel;
  29.         }
  30.         return value;
  31.     }
  32.     private void insertNodeHorizontally(SkipListNode pre, SkipListNode now) {
  33.         //先考慮now
  34.         now.next = pre.next;
  35.         now.pre = pre;
  36.         //再考慮pre的next節(jié)點
  37.         if (pre.next != null) {
  38.             pre.next.pre = now;
  39.         }
  40.         //最后考慮pre
  41.         pre.next = now;
  42.     }

刪除

使用 get 方法找到元素,然后解除節(jié)點屬性 levelNode 在每一層索引的前后引用關系即可

 
 
 
 
  1. //SkipList
  2.     public T remove(double score){
  3.         //在底層找到對應這個key的節(jié)點
  4.         SkipListNode now = get(score);
  5.         if (now == null) {
  6.             return null;
  7.         }
  8.         SkipListNode curNode, next;
  9.         //解除節(jié)點屬性 levelNode 在每一層索引的前后引用關系
  10.         for (int i = 0; i <= now.level; i++){
  11.             curNode = now.levelNode[i];
  12.             next = curNode.next;
  13.             if (next != null) {
  14.                 next.pre = curNode.pre;
  15.             }
  16.             curNode.pre.next = curNode.next;
  17.         }
  18.         this.length--; //更新size,返回舊值
  19.         return now.value;
  20.     }

使用示例

 
 
 
 
  1. public static void main(String[] args) {
  2.     SkipList list=new SkipList<>();
  3.     list.printSkipList();
  4.     list.put(1, "csc");
  5.     list.printSkipList();
  6.     list.put(3, "lwl");
  7.     list.printSkipList();
  8.     list.put(2, "hello world!");
  9.     list.printSkipList();
  10.     System.out.println(list.get(2));
  11.     System.out.println(list.get(4));
  12.     list.remove(2);
  13.     list.printSkipList();
  14. }

歡迎指正文中錯誤

參考文章

  • redis設計與實現
  • 跳表(跳躍表,skipList)總結-java版[1]
  • 數據結構與算法——跳表[2]

當前標題:數據結構:跳躍鏈表
轉載注明:http://www.dlmjj.cn/article/djcpgsj.html