容器 - 跳跃表

简述

跳表的本质是同时维护了多个链表,并且链表是分层的,最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。
eg. 在跳表中查找元素 18
在这里插入图片描述

查询、插入、删除的时间复杂度都是O(logn)。

在单链表中,一旦定位到要插入的位置,那么插入节点的时间复杂度很低 O(1),但是查找插入位置比较消耗时间。对于跳表而言,查找的时间复杂度是 O(logn),所以查找某个数据应该插入的位置时间复杂度也是 O(logn),删除同样。

注意: 当不停的在跳表中增加数据,如不更新索引,极端的情况下,会出现两个索引节点之间数据非常多的情况,退化成单链表

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

跳表使用的是空间换时间的思想,通过构建多级索引来提高查询效率,实现基于链表的“二分查找”,跳表是一种动态的数据结构,支持快速的查找、插入和删除操作,时间复杂度是 O(logn)。

跳表的空间复杂度是 O(n),不过跳表可以通过改变索引策略,动态的平衡执行效率和内存消耗。

源码如下

1. Node节点

class SkipListEntry{
 // data
    public String key;
    public Integer value;

    // links
    public SkipListEntry up;
    public SkipListEntry down;
    public SkipListEntry left;
    public SkipListEntry right;

    // special
    public static final String negInf = "-oo";  //用来处理一些特殊节点的初始化的
    public static final String posInf = "+oo";

    // constructor
    public SkipListEntry(String key, Integer value) {
        this.key = key;
        this.value = value;
    }
    // methods...
}

2. 跳跃表本身

public class SkipList {

    public SkipListEntry head;  // First element of the top level
    public SkipListEntry tail;  // Last element of the top level

    public int n;       // number of entries in the Skip List
    public int h;       // Height

    public Random r;    // 决定新添加的节点是否能够向更高一层的链表攀升
}

3. findEntry():定位一个元素所在位置

private SkipListEntry findEntry(String key) {

    SkipListEntry p;
    p = head;      // 从head头节点开始查找

    while(true) {
        // 从左向右查找,直到右节点的key值大于要查找的key值
        while(p.right.key != SkipListEntry.posInf && p.right.key.compareTo(key) <= 0) {
            p = p.right;
        }

        // 如果有更低层的节点,则向低层移动
        if(p.down != null) {
            p = p.down;
        } else {
            break;
        }
    }
    // 返回p,!注意这里p的key值是小于等于传入key的值的(p.key <= key)
    return p;
}

注:如果传入的key值在跳跃表中存在,则findEntry返回该对象的底层节点;
如不存在,则findEntry返回跳跃表中key值小于key,并且key值相差最小的底层节点;

4. get()方法

public Integer get(String key) {

    SkipListEntry p;

    p = findEntry(key);

    if(p.key.equals(key)) {
        return p.value;
    } else {
        return null;
    }
}

5. put()方法

public Integer put(String key, Integer value) {

    SkipListEntry p, q;
    int i = 0;

    // 查找适合插入的位子
    p = findEntry(key);

    // 如果跳跃表中存在含有key值的节点,则进行value的修改操作即可完成
    if(p.key.equals(key)) {
        Integer oldValue = p.value;
        p.value = value;
        return oldValue;
    }

    // 如果跳跃表中不存在含有key值的节点,则进行新增操作
    q = new SkipListEntry(key, value);
    q.left = p;
    q.right = p.right;
    p.right.left = q;
    p.right = q;

    // 再使用随机数决定是否要向更高level攀升
    while(r.nextDouble() < 0.5) {

        // 如果新元素的级别已经达到跳跃表的最大高度,则新建空白层
        if(i >= h) {
            addEmptyLevel();
        }

        // 从p向左扫描含有高层节点的节点
        while(p.up == null) {
            p = p.left;
        }
        p = p.up;

        // 新增和q指针指向的节点含有相同key值的节点对象
        // 这里需要注意的是除底层节点之外的节点对象是不需要value值的
        SkipListEntry z = new SkipListEntry(key, null);

        z.left = p;
        z.right = p.right;
        p.right.left = z;
        p.right = z;

        z.down = q;
        q.up = z;

        q = z;
        i = i + 1;
    }

    n = n + 1;

    // 返回null,没有旧节点的value值
    return null;
}

注:只要随机数满足条件,key=42的节点就会一直向上攀升,直到它的level等于跳跃表的高度(height)。这个时候需要在跳跃表的最顶层添加一个空白层,同时跳跃表的height+1,

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值