跳表(Skip List)

一. 跳表的定义

  1. 跳表,又叫做跳跃表、跳跃列表,在有序链表的基础上增加了“跳跃”的功能
  2. 跳表在原来的有序链表上加上了多级索引,通过索引来快速查找;可以支持快速的删除、插入和查找操作。
  3. 跳表实际上是一种增加了前向指针的链表,是一种随机化的数据结构
  4. Redis中 的 SortedSet、LevelDB 中的 MemTable 都用到了跳表
  5. 对比平衡树, 跳表的实现和维护会更加简单, 跳表的搜索、删除、添加的平均时间复杂度是 O(logn)

二. 跳表的数据结构图型

使用跳表优化链表

  • 对于一个单链表来讲,即使链表中存储的数据是有序的,如果我们想要在其中查找某个数据,也只能从头开到尾的遍历,查询效率低,时间复杂度是O(n)。

在这里插入图片描述

三. 跳表的搜索

跳表查找任意数据的时间复杂度为O(logn)

  1. 从顶层链表的首元素开始,从左往右搜索,直至找到一个大于或等于目标的元素,或者到达当前层链表的尾部
  2. 如果该元素等于目标元素,则表明该元素已被找到
  3. 如果该元素大于目标元素或已到达链表的尾部,则退回到当前层的前一个元素,然后转入下一层进行搜索

四. 跳表的插入

跳表插入的时间复杂度为:O(logn),支持高效的动态插入。

在这里插入图片描述

五. 跳表的删除

跳表的删除操作时间复杂度为:O(logn),支持动态的删除。

  • 在跳表中删除某个结点时,如果这个结点在索引中也出现了,我们除了要删除原始链表中的结点,还要删除索引中的。因为单链表中的删除操作需要拿到删除结点的前驱结点,然后再通过指针操作完成删除。所以在查找要删除的结点的时候,一定要获取前驱结点(双向链表除外)。因此跳表的删除操作时间复杂度即为O(logn)。

六. 跳表索引动态更新

  • 当我们不断地往跳表中插入数据时,我们如果不更新索引,就有可能出现某2个索引节点之间的数据非常多的情况,在极端情况下,跳表还会退化成单链表

在这里插入图片描述

跳表是通过随机函数来维护“平衡性”。

  • 当我们在跳表中插入数据的时候,我们通过选择同时将这个数据插入到部分索引层中,如何选择索引层,可以通过一个随机函数来决定这个节点插入到哪几级索引中,比如随机生成了k,那么就将这个索引加入到,第一级到第k级索引中。

在这里插入图片描述

七. 跳表的性质

  1. 跳表由很多层结构组成,level是通过一定的概率随机产生的;
  2. 每一层都是一个有序的链表,默认是升序 ;
  3. 最底层(Level 1)的链表包含所有元素;
  4. 如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现;
  5. 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

八.Java实现跳表

import java.util.Random;

/**
 * Copyright (c) 2023,
 * All rights reserved
 * Author: hakusai22@qq.com
 */
public class SkipList {
  // 跳表中存储的是正整数,并且存储的数据是不重复的
  private static final int MAX_LEVEL = 16;    // 结点的个数
  private int levelCount = 1;   // 索引的层级数
  private final Node head = new Node();    // 头结点
  private final Random random = new Random();

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

    if (p.next[0] != null && p.next[0].data == value) {
      return p.next[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;
    }

    Node p = head;
    for (int i = level - 1; i >= 0; --i) {
      while (p.next[i] != null && p.next[i].data < value) {
        p = p.next[i];
      }
      update[i] = p;
    }

    for (int i = 0; i < level; ++i) {
      newNode.next[i] = update[i].next[i];
      update[i].next[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.next[i] != null && p.next[i].data < value) {
        p = p.next[i];
      }
      update[i] = p;
    }

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

  // 随机函数
  private int randomLevel() {
    int level = 1;
    for (int i = 1; i < MAX_LEVEL; ++i) {
      if (random.nextInt() % 2 == 1) {
        level++;
      }
    }

    return level;
  }

  // Node内部类
  public static class Node {
    private int data = -1;
    private final Node[] next = new Node[MAX_LEVEL];
    private int maxLevel = 0;

    // 重写toString方法
    @Override
    public String toString() {
      return "{data:" +
          data +
          "; levels: " +
          maxLevel +
          " }";
    }
  }

  // 显示跳表中的结点
  public void display() {
    Node p = head;
    while (p.next[0] != null) {
      System.out.println(p.next[0] + " ");
      p = p.next[0];
    }
    System.out.println();
  }

}

测试自定义跳表

在这里插入图片描述

最后总结跳表

  • 跳表使用的是空间换时间的思想,通过构建多级索引来提高查询效率,实现基于链表的“二分查找”,跳表是一种动态的数据结构,支持快速的查找、插入和删除操作,时间复杂度是 O(logn)。
  • 跳表的空间复杂度是 O(n),不过跳表可以通过改变索引策略,动态的平衡执行效率和内存消耗。
  • 58
    点赞
  • 216
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
跳表Skiplist)是一种基于链表实现的数据结构,用于快速查找和插入有序序列中的元素。它是一种随机化数据结构,可以在O(log n)的时间内完成查找、插入和删除操作。 以下是一个简单的C++实现示例: ```c++ #include <iostream> #include <cstdlib> #include <ctime> using namespace std; const int MAX_LEVEL = 16; // 最大层数 class Node { public: int key; Node **forward; Node(int level, int key) { forward = new Node*[level+1]; memset(forward, 0, sizeof(Node*)*(level+1)); this->key = key; } ~Node() { delete[] forward; } }; class SkipList { public: SkipList() { levelCount = 1; head = new Node(MAX_LEVEL, 0); srand(time(0)); } ~SkipList() { delete head; } int randomLevel() { int level = 1; while (rand() % 2 == 1 && level < MAX_LEVEL) level++; return level; } void insert(int key) { Node *update[MAX_LEVEL+1]; memset(update, 0, sizeof(Node*)*(MAX_LEVEL+1)); Node *p = head; for (int i = levelCount; i >= 1; i--) { while (p->forward[i] != nullptr && p->forward[i]->key < key) p = p->forward[i]; update[i] = p; } p = p->forward[1]; if (p != nullptr && p->key == key) return; int level = randomLevel(); if (level > levelCount) { for (int i = levelCount+1; i <= level; i++) update[i] = head; levelCount = level; } p = new Node(level, key); for (int i = 1; i <= level; i++) { p->forward[i] = update[i]->forward[i]; update[i]->forward[i] = p; } } void remove(int key) { Node *update[MAX_LEVEL+1]; memset(update, 0, sizeof(Node*)*(MAX_LEVEL+1)); Node *p = head; for (int i = levelCount; i >= 1; i--) { while (p->forward[i] != nullptr && p->forward[i]->key < key) p = p->forward[i]; update[i] = p; } p = p->forward[1]; if (p == nullptr || p->key != key) return; for (int i = 1; i <= levelCount; i++) { if (update[i]->forward[i] != p) break; update[i]->forward[i] = p->forward[i]; } delete p; while (levelCount > 1 && head->forward[levelCount] == nullptr) levelCount--; } bool search(int key) { Node *p = head; for (int i = levelCount; i >= 1; i--) { while (p->forward[i] != nullptr && p->forward[i]->key < key) p = p->forward[i]; } p = p->forward[1]; if (p != nullptr && p->key == key) return true; return false; } void display() { for (int i = 1; i <= levelCount; i++) { Node *p = head->forward[i]; cout << "Level " << i << ": "; while (p != nullptr) { cout << p->key << " "; p = p->forward[i]; } cout << endl; } } private: Node *head; int levelCount; }; int main() { SkipList skiplist; skiplist.insert(1); skiplist.insert(3); skiplist.insert(2); skiplist.insert(4); skiplist.display(); skiplist.remove(3); skiplist.display(); cout << skiplist.search(2) << endl; cout << skiplist.search(3) << endl; return 0; } ``` 在这个示例中,我们使用了一个类`Node`作为跳表中的节点,`SkipList`类则封装了跳表的插入、删除、搜索和显示等操作。其中,`randomLevel()`函数用于随机生成节点的层数,`insert()`函数用于插入一个节点,`remove()`函数用于删除一个节点,`search()`函数用于查找一个节点,`display()`函数用于显示整个跳表跳表是一种比较高级的数据结构,它可以在很多场景中代替平衡树,以提高数据结构操作的效率。如果你对跳表感兴趣,可以尝试阅读一些更深入的资料来了解更多。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值