数据结构与算法-跳表

跳表是什么:

跳表是链表+链表索引构成的一种数据结构

跳表有什么用:

跳表是优化链表查找效率的一种数据结构。

链表在查找时时间复杂度是O(n),数据量大时,效率不高,要想提升查找效率,非常直接的思路就是要减少需要遍历的元素个数。

优化思路:

  • 借鉴数组的二分查找算法,基于原始链表构建一个新的数量大幅减少的链表,称之为子链表或者索引链表,可能有多层,见下图。
    在这里插入图片描述

  • 如图所示,上层链表基于下层链表的每两个节点选取第一个节点,将这些节点首尾相连,构成一个新链表。随着层数的增高,链表的节点数逐渐减少。
    当然,也可以每三个(或者更多)节点选取一个节点,视情况而定。

  • 优化后,在查找时,从最上层开始查找,最好的情况是在最上层查找成功,最坏的情况是要在最下层查找,但即便是最坏的情况,也会大大缩小查询范围。

查找时间复杂度:

  • 优化后的查询复杂度是O(logn),和二分查找的时间复杂度是一样的。

  • 要理解这个时间复杂度,关键在于理解,每层上最多需要遍历多少个节点,如果是每两个节点取一个节点构成的链表,每层只需要遍历3个节点,看图即可理解。

  • 最多有多少层呢?由公式计算而出最多 log(n)层,则整个搜索过程最多需要遍历 3*log(n)次,即算法时间复杂度位log(n)

特点:

  • 典型的用空间换时间
  • 原链表是有序链表
  • 上层链表是下次链表的子集,上层链表的节点一定存在于下层链表

代码:

package skiplist;


/**
 * 跳表的一种实现方法。
 * 跳表中存储的是正整数,并且存储的是不重复的。
 *
 * Author:ZHENG
 */
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--;
    }

  }

 // 插入元素时,从第几层开始插入,是随机选取的,但也是有一定技巧的,底层的概率高,高层的概率低
  // 理论来讲,一级索引中元素个数应该占原始数据的 50%,二级索引中元素个数占 25%,三级索引12.5% ,一直到最顶层。
  // 因为这里每一层的晋升概率是 50%。对于每一个新插入的节点,都需要调用 randomLevel 生成一个合理的层数。
  // 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 :
  //        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();
    }
  }

}

代码总结:

  • 插入时根据随机函数选择从第几层开始插入,随机函数巧妙
  • 链表的层次包含在Node节点的定义中
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小手追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值