亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線(xiàn)咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

 

前言

在頭條創(chuàng)作了一個(gè)月左右的時(shí)間,收獲了50+粉絲,很是開(kāi)心,我會(huì)把數(shù)據(jù)結(jié)構(gòu)與算法的文章更新到底,第一次看我文章的同仁如果覺(jué)得不錯(cuò)的話(huà)就關(guān)注一下我哦,你的支持就是我創(chuàng)作的動(dòng)力。

 

一、什么是跳表

我們?cè)谥敖榻B了十分優(yōu)秀的二分查找算法,但是它只能作用于有序數(shù)組上,查找起來(lái)比較方便,但是數(shù)組中插入和刪除元素是比較麻煩的;那么有沒(méi)有辦法讓二分查找也能作用于有序鏈表上,從而達(dá)到查找、插入和刪除元素都十分的快速呢?

對(duì)于普通的有序列表來(lái)說(shuō),當(dāng)然不能實(shí)現(xiàn)我們的目標(biāo),如下查找的時(shí)間復(fù)雜度為O(n);

原始有序單鏈表

我們可以基于原始鏈表建立一個(gè) 索引層,比如每?jī)蓚€(gè)節(jié)點(diǎn)提取一個(gè)節(jié)點(diǎn)到索引層:

帶一級(jí)索引的跳表

如此,兩種數(shù)據(jù)結(jié)構(gòu)我們查找元素16的比較次數(shù)分別為10次和8次,確實(shí)能提高查詢(xún)速度;我們更近一步,再次建立第二級(jí)索引:

帶二級(jí)索引的跳表 帶二級(jí)索引的跳表

此時(shí)查找元素16比較的次數(shù)只需要比較7次即可;

如果在大數(shù)據(jù)量的有序鏈表中,我們建立很多層索引,使得最高層索引只有兩個(gè)節(jié)點(diǎn),那么就實(shí)現(xiàn)了類(lèi)似二分查找的算法思想,此時(shí)這種數(shù)據(jù)結(jié)構(gòu)就被成為跳表。

二、跳表性能分析

2.1 時(shí)間復(fù)雜度

假設(shè)有序鏈表總節(jié)點(diǎn)個(gè)數(shù)為n,我們建立索引時(shí)每?jī)蓚€(gè)節(jié)點(diǎn)提取一個(gè)索引節(jié)點(diǎn);那么第一級(jí)索引共有n/2個(gè)節(jié)點(diǎn);

第k級(jí)索引共有(n/2^k)個(gè)節(jié)點(diǎn),假設(shè)k為最高級(jí)索引,為2個(gè)節(jié)點(diǎn),那么2=(n/2^k),n=2(k+1),k=logn-1,如果原始鏈表也算進(jìn)去的話(huà),k=logn正好是整個(gè)數(shù)據(jù)結(jié)構(gòu)的高度。

假設(shè)每一層需要遍歷m個(gè)節(jié)點(diǎn),那么時(shí)間復(fù)雜度就可以表示為O(mlogn),但是推斷m是常量級(jí)別的,因此可以忽略,那么整個(gè)查找過(guò)程時(shí)間復(fù)雜度就是O(logn),竟然和二分查找是一樣的高效!

2.2 空間復(fù)雜度

第一級(jí)索引需要n/2個(gè)節(jié)點(diǎn),第二級(jí)需要n/2^2個(gè)節(jié)點(diǎn),依次類(lèi)推,第k級(jí)索引需要的節(jié)點(diǎn)個(gè)數(shù)為n/2^k,這正好是一個(gè)等比數(shù)列,等比數(shù)列求和的結(jié)果是n-2,所以空間復(fù)雜度為O(n),這是一個(gè)以空間換時(shí)間的策略;

我們可以每3個(gè)節(jié)點(diǎn)或者每4個(gè)節(jié)點(diǎn)往上提取一個(gè)索引節(jié)點(diǎn),如此可以適當(dāng)減少需要的額外空間,但是空間復(fù)雜度仍然為O(n);如果有序鏈表存儲(chǔ)的是大對(duì)象,那么索引節(jié)點(diǎn)中無(wú)需存放整個(gè)大對(duì)象,只要存儲(chǔ)對(duì)象的指針即可,所以此時(shí)空間復(fù)雜度就顯得不那么重要了;

2.3 跳表的插入和刪除

跳表因?yàn)槭琼樞蜴湵恚哉嬲迦牒蛣h除的時(shí)間復(fù)雜度都是O(1),但是找到需要插入節(jié)點(diǎn)的位置或者找到待刪除的節(jié)點(diǎn)時(shí)間復(fù)雜度為O(logn);

跳表在刪除的時(shí)候,除了需要?jiǎng)h除原始有序鏈表中的節(jié)點(diǎn),還需要同步刪除k級(jí)索引中的全部該索引節(jié)點(diǎn);

跳表在插入元素式,極端情況下會(huì)導(dǎo)致兩個(gè)索引節(jié)點(diǎn)中存在大量的原始節(jié)點(diǎn),時(shí)間效率極有可能會(huì)退化為單鏈表的O(n),所以需要?jiǎng)討B(tài)地平衡和更新k級(jí)索引節(jié)點(diǎn);

三、跳表使用場(chǎng)景

redis在存儲(chǔ)有序集合的時(shí)候就用到了跳表+散列表的數(shù)據(jù)結(jié)構(gòu),跳表和紅黑樹(shù)相比,插入、刪除、查找的時(shí)間復(fù)雜度相同,但是跳表在按照區(qū)間查找時(shí)明顯具有效率優(yōu)勢(shì),而且跳表實(shí)現(xiàn)起來(lái)比紅黑樹(shù)要簡(jiǎn)單易懂,不容易出錯(cuò)。

紅黑樹(shù)等常用數(shù)據(jù)結(jié)構(gòu)在程序語(yǔ)言中都是封裝好的,我們想用的話(huà)直接拿來(lái)用即可,比如HashMap,但是跳表卻沒(méi)有對(duì)應(yīng)的封裝好的數(shù)據(jù)結(jié)構(gòu),想用的話(huà)開(kāi)發(fā)者必須自己去實(shí)現(xiàn)。

四、代碼實(shí)現(xiàn)跳表Skiplist以及優(yōu)化

代碼來(lái)源于極客時(shí)間《數(shù)據(jù)結(jié)構(gòu)和算法之美》

github地址:
https://github.com/wangzheng0822/algo(數(shù)據(jù)結(jié)構(gòu)和算法必知必會(huì)的50個(gè)代碼實(shí)現(xiàn))

4.1 作者王爭(zhēng)給出的跳表實(shí)現(xiàn)方式

/**
 * 跳表的一種實(shí)現(xiàn)方法。
 * 跳表中存儲(chǔ)的是正整數(shù),并且存儲(chǔ)的是不重復(fù)的。
 *
 */
public class SkipList {

  private static final float SKIPLIST_P = 0.5f;
  private static final int MAX_LEVEL = 16;

  private int levelCount = 1;

  private Node head = new Node();  // 帶頭鏈表

  public Node find(int value) {
    Node p = head;
    for (int i = levelCount - 1; i >= 0; --i) {
      while (p.forwards[i] != null && p.forwards[i].data < value) {
        p = p.forwards[i];
      }
    }

    if (p.forwards[0] != null && p.forwards[0].data == value) {
      return p.forwards[0];
    } else {
      return null;
    }
  }

  public void insert(int value) {
    int level = randomLevel();
    Node newNode = new Node();
    newNode.data = value;
    newNode.maxLevel = level;
    Node update[] = new Node[level];
    for (int i = 0; i < level; ++i) {
      update[i] = head;
    }

    // record every level largest value which smaller than insert value in update[]
    Node p = head;
    for (int i = level - 1; i >= 0; --i) {
      while (p.forwards[i] != null && p.forwards[i].data < value) {
        p = p.forwards[i];
      }
      update[i] = p;// use update save node in search path
    }

    // in search path node next node become new node forwords(next)
    for (int i = 0; i < level; ++i) {
      newNode.forwards[i] = update[i].forwards[i];
      update[i].forwards[i] = newNode;
    }

    // update node hight
    if (levelCount < level) levelCount = level;
  }

  public void delete(int value) {
    Node[] update = new Node[levelCount];
    Node p = head;
    for (int i = levelCount - 1; i >= 0; --i) {
      while (p.forwards[i] != null && p.forwards[i].data < value) {
        p = p.forwards[i];
      }
      update[i] = p;
    }

    if (p.forwards[0] != null && p.forwards[0].data == value) {
      for (int i = levelCount - 1; i >= 0; --i) {
        if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {
          update[i].forwards[i] = update[i].forwards[i].forwards[i];
        }
      }
    }

    while (levelCount>1&&head.forwards[levelCount]==null){
      levelCount--;
    }

  }

  // 理論來(lái)講,一級(jí)索引中元素個(gè)數(shù)應(yīng)該占原始數(shù)據(jù)的 50%,二級(jí)索引中元素個(gè)數(shù)占 25%,三級(jí)索引12.5% ,一直到最頂層。
  // 因?yàn)檫@里每一層的晉升概率是 50%。對(duì)于每一個(gè)新插入的節(jié)點(diǎn),都需要調(diào)用 randomLevel 生成一個(gè)合理的層數(shù)。
  // 該 randomLevel 方法會(huì)隨機(jī)生成 1~MAX_LEVEL 之間的數(shù),且 :
  //        50%的概率返回 1
  //        25%的概率返回 2
  //      12.5%的概率返回 3 ...
  private int randomLevel() {
    int level = 1;

    while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)
      level += 1;
    return level;
  }

  public void printAll() {
    Node p = head;
    while (p.forwards[0] != null) {
      System.out.print(p.forwards[0] + " ");
      p = p.forwards[0];
    }
    System.out.println();
  }

  public class Node {
    private int data = -1;
    private Node forwards[] = new Node[MAX_LEVEL];
    private int maxLevel = 0;

    @Override
    public String toString() {
      StringBuilder builder = new StringBuilder();
      builder.Append("{ data: ");
      builder.append(data);
      builder.append("; levels: ");
      builder.append(maxLevel);
      builder.append(" }");

      return builder.toString();
    }
  }

}

4.2 作者ldb基于王爭(zhēng)的代碼給出的優(yōu)化

import JAVA.util.Random;

/**
 * 1,跳表的一種實(shí)現(xiàn)方法,用于練習(xí)。跳表中存儲(chǔ)的是正整數(shù),并且存儲(chǔ)的是不重復(fù)的。
 * 2,本類(lèi)是參考作者zheng ,自己學(xué)習(xí),優(yōu)化了添加方法
 * 3,看完這個(gè),我覺(jué)得再看ConcurrentSkipListMap 源碼,會(huì)有很大收獲
 */
public class SkipList2 {

    private static final int MAX_LEVEL = 16;
    private int levelCount = 1;

    /**
     * 帶頭鏈表
     */
    private Node head = new Node(MAX_LEVEL);
    private Random r = new Random();

    public Node find(int value) {
        Node p = head;
        // 從最大層開(kāi)始查找,找到前一節(jié)點(diǎn),通過(guò)--i,移動(dòng)到下層再開(kāi)始查找
        for (int i = levelCount - 1; i >= 0; --i) {
            while (p.forwards[i] != null && p.forwards[i].data < value) {
                // 找到前一節(jié)點(diǎn)
                p = p.forwards[i];
            }
        }

        if (p.forwards[0] != null && p.forwards[0].data == value) {
            return p.forwards[0];
        } else {
            return null;
        }
    }

    /**
     * 優(yōu)化了作者zheng的插入方法
     *
     * @param value 值
     */
    public void insert(int value) {
        int level = head.forwards[0] == null ? 1 : randomLevel();
        // 每次只增加一層,如果條件滿(mǎn)足
        if (level > levelCount) {
            level = ++levelCount;
        }
        Node newNode = new Node(level);
        newNode.data = value;
        Node update[] = new Node[level];
        for (int i = 0; i < level; ++i) {
            update[i] = head;
        }

        Node p = head;
        // 從最大層開(kāi)始查找,找到前一節(jié)點(diǎn),通過(guò)--i,移動(dòng)到下層再開(kāi)始查找
        for (int i = levelCount - 1; i >= 0; --i) {
            while (p.forwards[i] != null && p.forwards[i].data < value) {
                // 找到前一節(jié)點(diǎn)
                p = p.forwards[i];
            }
            // levelCount 會(huì) > level,所以加上判斷
            if (level > i) {
                update[i] = p;
            }

        }
        for (int i = 0; i < level; ++i) {
            newNode.forwards[i] = update[i].forwards[i];
            update[i].forwards[i] = newNode;
        }

    }

    /**
     * 優(yōu)化了作者zheng的插入方法2
     *
     * @param value 值
     */
    public void insert2(int value) {
        int level = head.forwards[0] == null ? 1 : randomLevel();
        // 每次只增加一層,如果條件滿(mǎn)足
        if (level > levelCount) {
            level = ++levelCount;
        }
        Node newNode = new Node(level);
        newNode.data = value;
        Node p = head;
        // 從最大層開(kāi)始查找,找到前一節(jié)點(diǎn),通過(guò)--i,移動(dòng)到下層再開(kāi)始查找
        for (int i = levelCount - 1; i >= 0; --i) {
            while (p.forwards[i] != null && p.forwards[i].data < value) {
                // 找到前一節(jié)點(diǎn)
                p = p.forwards[i];
            }
            // levelCount 會(huì) > level,所以加上判斷
            if (level > i) {
                if (p.forwards[i] == null) {
                    p.forwards[i] = newNode;
                } else {
                    Node next = p.forwards[i];
                    p.forwards[i] = newNode;
                    newNode.forwards[i] = next;
                }
            }

        }

    }

    /**
     * 作者zheng的插入方法,未優(yōu)化前,優(yōu)化后參見(jiàn)上面insert()
     *
     * @param value
     * @param level 0 表示隨機(jī)層數(shù),不為0,表示指定層數(shù),指定層數(shù)
     *              可以讓每次打印結(jié)果不變動(dòng),這里是為了便于學(xué)習(xí)理解
     */
    public void insert(int value, int level) {
        // 隨機(jī)一個(gè)層數(shù)
        if (level == 0) {
            level = randomLevel();
        }
        // 創(chuàng)建新節(jié)點(diǎn)
        Node newNode = new Node(level);
        newNode.data = value;
        // 表示從最大層到低層,都要有節(jié)點(diǎn)數(shù)據(jù)
        newNode.maxLevel = level;
        // 記錄要更新的層數(shù),表示新節(jié)點(diǎn)要更新到哪幾層
        Node update[] = new Node[level];
        for (int i = 0; i < level; ++i) {
            update[i] = head;
        }

        /**
         *
         * 1,說(shuō)明:層是從下到上的,這里最下層編號(hào)是0,最上層編號(hào)是15
         * 2,這里沒(méi)有從已有數(shù)據(jù)最大層(編號(hào)最大)開(kāi)始找,(而是隨機(jī)層的最大層)導(dǎo)致有些問(wèn)題。
         *    如果數(shù)據(jù)量為1億,隨機(jī)level=1 ,那么插入時(shí)間復(fù)雜度為O(n)
         */
        Node p = head;
        for (int i = level - 1; i >= 0; --i) {
            while (p.forwards[i] != null && p.forwards[i].data < value) {
                p = p.forwards[i];
            }
            // 這里update[i]表示當(dāng)前層節(jié)點(diǎn)的前一節(jié)點(diǎn),因?yàn)橐业角耙还?jié)點(diǎn),才好插入數(shù)據(jù)
            update[i] = p;
        }

        // 將每一層節(jié)點(diǎn)和后面節(jié)點(diǎn)關(guān)聯(lián)
        for (int i = 0; i < level; ++i) {
            // 記錄當(dāng)前層節(jié)點(diǎn)后面節(jié)點(diǎn)指針
            newNode.forwards[i] = update[i].forwards[i];
            // 前一個(gè)節(jié)點(diǎn)的指針,指向當(dāng)前節(jié)點(diǎn)
            update[i].forwards[i] = newNode;
        }

        // 更新層高
        if (levelCount < level) levelCount = level;
    }

    public void delete(int value) {
        Node[] update = new Node[levelCount];
        Node p = head;
        for (int i = levelCount - 1; i >= 0; --i) {
            while (p.forwards[i] != null && p.forwards[i].data < value) {
                p = p.forwards[i];
            }
            update[i] = p;
        }

        if (p.forwards[0] != null && p.forwards[0].data == value) {
            for (int i = levelCount - 1; i >= 0; --i) {
                if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {
                    update[i].forwards[i] = update[i].forwards[i].forwards[i];
                }
            }
        }
    }

    /**
     * 隨機(jī) level 次,如果是奇數(shù)層數(shù) +1,防止偽隨機(jī)
     *
     * @return
     */
    private int randomLevel() {
        int level = 1;
        for (int i = 1; i < MAX_LEVEL; ++i) {
            if (r.nextInt() % 2 == 1) {
                level++;
            }
        }
        return level;
    }

    /**
     * 打印每個(gè)節(jié)點(diǎn)數(shù)據(jù)和最大層數(shù)
     */
    public void printAll() {
        Node p = head;
        while (p.forwards[0] != null) {
            System.out.print(p.forwards[0] + " ");
            p = p.forwards[0];
        }
        System.out.println();
    }

    /**
     * 打印所有數(shù)據(jù)
     */
    public void printAll_beautiful() {
        Node p = head;
        Node[] c = p.forwards;
        Node[] d = c;
        int maxLevel = c.length;
        for (int i = maxLevel - 1; i >= 0; i--) {
            do {
                System.out.print((d[i] != null ? d[i].data : null) + ":" + i + "-------");
            } while (d[i] != null && (d = d[i].forwards)[i] != null);
            System.out.println();
            d = c;
        }
    }

    /**
     * 跳表的節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)記錄了當(dāng)前節(jié)點(diǎn)數(shù)據(jù)和所在層數(shù)數(shù)據(jù)
     */
    public class Node {
        private int data = -1;
        /**
         * 表示當(dāng)前節(jié)點(diǎn)位置的下一個(gè)節(jié)點(diǎn)所有層的數(shù)據(jù),從上層切換到下層,就是數(shù)組下標(biāo)-1,
         * forwards[3]表示當(dāng)前節(jié)點(diǎn)在第三層的下一個(gè)節(jié)點(diǎn)。
         */
        private Node forwards[];

        /**
         * 這個(gè)值其實(shí)可以不用,看優(yōu)化insert()
         */
        private int maxLevel = 0;

        public Node(int level) {
            forwards = new Node[level];
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("{ data: ");
            builder.append(data);
            builder.append("; levels: ");
            builder.append(maxLevel);
            builder.append(" }");
            return builder.toString();
        }
    }

    public static void main(String[] args) {
        SkipList2 list = new SkipList2();
        list.insert(1, 3);
        list.insert(2, 3);
        list.insert(3, 2);
        list.insert(4, 4);
        list.insert(5, 10);
        list.insert(6, 4);
        list.insert(8, 5);
        list.insert(7, 4);
        list.printAll_beautiful();
        list.printAll();
        /**
         * 結(jié)果如下:
         * null:15-------
         * null:14-------
         * null:13-------
         * null:12-------
         * null:11-------
         * null:10-------
         * 5:9-------
         * 5:8-------
         * 5:7-------
         * 5:6-------
         * 5:5-------
         * 5:4-------                    
         * 8:4-------
         * 4:3-------5:3-------6:3-------7:3-------8:3-------
         * 1:2-------2:2-------          4:2-------5:2-------6:2-------7:2-------8:2-------
         * 1:1-------2:1-------3:1-------4:1-------5:1-------6:1-------7:1-------8:1-------
         * 1:0-------2:0-------3:0-------4:0-------5:0-------6:0-------7:0-------8:0-------
         * { data: 1; levels: 3 } { data: 2; levels: 3 } { data: 3; levels: 2 } { data: 4; levels: 4 }
         * { data: 5; levels: 10 } { data: 6; levels: 4 } { data: 7; levels: 4 } { data: 8; levels: 5 }
         */
        // 優(yōu)化后insert()

        SkipList2 list2 = new SkipList2();
        list2.insert2(1);
        list2.insert2(2);
        list2.insert2(6);
        list2.insert2(7);
        list2.insert2(8);
        list2.insert2(3);
        list2.insert2(4);
        list2.insert2(5);
        System.out.println();
        list2.printAll_beautiful();


    }

}

分享到:
標(biāo)簽:數(shù)據(jù)結(jié)構(gòu)
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定