原来这就是传说中的跳跃表,不过如此(附:跳跃表的Java实现)

Posted 守夜人爱吃兔子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原来这就是传说中的跳跃表,不过如此(附:跳跃表的Java实现)相关的知识,希望对你有一定的参考价值。

1. 什么是跳跃表?

增加了向前指针的链表叫作跳表。跳表全称叫做跳跃表。跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。(摘自百度百科)

2. Redis中跳跃表的数据结构

//zskiplistNode:跳跃表的一个节点
typedef struct zskiplistNode {
    // 层:每个节点都包含很多层,每一层指向的都是同一个对象
    struct zskiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度:指当前这一层两个节点的距离,如上图o1和o3节点的L3层,中间跨过了o2节点,所以跨度就为2
        unsigned int span;
    } level[];
    // 后退指针
    struct zskiplistNode *backward;
    // 分值:用于排序,跳跃表中的所有节点都按分值大小进行排序
    double score;
    // 成员对象:即真正要往链表中存放的对象
    robj *obj;
} zskiplistNode;

//zskiplist:跳跃表链表
typedef struct zskiplist {
    // 表头节点和表尾节点
    structz skiplistNode *header, *tail;
    // 表中节点的数量
    unsigned long length;
    // 表中层数最大的节点的层数
    int level;
} zskiplist;

3. 总结

跳跃表是有序集合的底层实现之一(zset)。

  • Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。

  • 每个跳跃表节点的层高都是1至32之间的随机数。

  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。

  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。

4. 附:跳跃表的Java实现

SkipList.java

import java.util.Random;

/**
 * @author DengWeiPing
 * @version 1.0
 * @date 2021/5/16 10:00
 */
public class SkipList<T> {
    private SkipListNode<T> head, tail;
    private int nodes;//节点总数
    private int listLevel;//层数
    private Random random;// 用于投掷硬币
    private static final double PROBABILITY = 0.5;//向上提升一个的概率

    public SkipList() {
        // TODO Auto-generated constructor stub
        random = new Random();
        clear();
    }

    /**
     * 清空跳跃表
     */
    public void clear() {
        head = new SkipListNode<T>(SkipListNode.HEAD_KEY, null);
        tail = new SkipListNode<T>(SkipListNode.TAIL_KEY, null);
        horizontalLink(head, tail);
        listLevel = 0;
        nodes = 0;
    }

    public boolean isEmpty() {
        return nodes == 0;
    }

    public int size() {
        return nodes;
    }

    /**
     * 在最下面一层,找到要插入的位置前面的那个key
     */
    private SkipListNode<T> findNode(int key) {
        SkipListNode<T> p = head;
        while (true) {
            while (p.right.key != SkipListNode.TAIL_KEY && p.right.key <= key) {
                p = p.right;
            }
            if (p.down != null) {
                p = p.down;
            } else {
                break;
            }

        }
        return p;
    }

    /**
     * 查找是否存在key,存在则返回该节点,否则返回null
     */
    public SkipListNode<T> search(int key) {
        SkipListNode<T> p = findNode(key);
        if (key == p.getKey()) {
            return p;
        } else {
            return null;
        }
    }

    /**
     * 向跳跃表中添加key-value
     */
    public void put(int k, T v) {
        SkipListNode<T> p = findNode(k);
        //如果key值相同,替换原来的vaule即可结束
        if (k == p.getKey()) {
            p.value = v;
            return;
        }
        SkipListNode<T> q = new SkipListNode<>(k, v);
        backLink(p, q);
        int currentLevel = 0;//当前所在的层级是0
        //抛硬币,如果小于0.5,则往上一层也插入q
        while (random.nextDouble() < PROBABILITY) {
            //如果此次仍是正面,且当前要插入的层数超出了高度,需要重新建一个顶层
            if (currentLevel >= listLevel) {
                listLevel++;
                SkipListNode<T> p1 = new SkipListNode<>(SkipListNode.HEAD_KEY, null);
                SkipListNode<T> p2 = new SkipListNode<>(SkipListNode.TAIL_KEY, null);
                horizontalLink(p1, p2);
                vertiacallLink(p1, head);
                vertiacallLink(p2, tail);
                head = p1;
                tail = p2;
            }
            //找到p的上一层,如果p没有上层,则找它左边节点的上一层
            while (p.up == null) {
                p = p.left;
            }
            p = p.up;//此时的p是上一层的节点

            SkipListNode<T> e = new SkipListNode<T>(k, null);//只保存key就ok
            backLink(p, e);//将e插入到上一层的p的右边
            vertiacallLink(e, q);//将e和q上下连接
            q = e;//此时就在上一层也插入了q,并且和其左边节点及下面节点相连接
            currentLevel++;
        }
        nodes++;//节点数+1
    }

    //node1后面插入node2
    private void backLink(SkipListNode<T> node1, SkipListNode<T> node2) {
        node2.left = node1;
        node2.right = node1.right;
        node1.right.left = node2;
        node1.right = node2;
    }

    /**
     * 水平双向连接
     */
    private void horizontalLink(SkipListNode<T> node1, SkipListNode<T> node2) {
        node1.right = node2;
        node2.left = node1;
    }

    /**
     * 垂直双向连接
     */
    private void vertiacallLink(SkipListNode<T> node1, SkipListNode<T> node2) {
        node1.down = node2;
        node2.up = node1;
    }

    /**
     * 打印出原始数据
     */
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        if (isEmpty()) {
            return "跳跃表为空!";
        }
        StringBuilder builder = new StringBuilder();
        SkipListNode<T> p = head;
        while (p.down != null) {
            p = p.down;
        }

        while (p.left != null) {
            p = p.left;
        }
        if (p.right != null) {
            p = p.right;
        }
        while (p.right != null) {
            builder.append(p);
            builder.append("\\n");
            p = p.right;
        }

        return builder.toString();
    }

}

SkipListNode.java

/**
 * @author DengWeiPing
 * @version 1.0
 * @date 2021/5/16 9:59
 */
public class SkipListNode<T> {
    public int key;
    public T value;
    public SkipListNode<T> up, down, left, right; // 上下左右 四个指针

    public static final int HEAD_KEY = Integer.MIN_VALUE; // 负无穷
    public static final int TAIL_KEY = Integer.MAX_VALUE; // 正无穷

    public SkipListNode(int k, T v) {
        // TODO Auto-generated constructor stub
        key = k;
        value = v;
    }

    public int getKey() {
        return key;
    }

    public void setKey(int key) {
        this.key = key;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if (!(o instanceof SkipListNode<?>)) {
            return false;
        }
        SkipListNode<T> ent;
        try {
            ent = (SkipListNode<T>) o; // 检测类型
        } catch (ClassCastException ex) {
            return false;
        }
        return (ent.getKey() == key) && (ent.getValue() == value);
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return "key-value:" + key + "-" + value;
    }

}

test.java

public static void main(String[] args) {
    // TODO Auto-generated method stub
    SkipList<String> list = new SkipList<String>();
    System.out.println(list);
    list.put(2, "ping");
    list.put(1, "deng");
    list.put(3, "wei");
    list.put(1, "d");//测试同一个key值
    list.put(4, "邓");
    list.put(6, "卫");
    list.put(5, "平");
    System.out.println(list);
    System.out.println(list.size());
}


 

以上是关于原来这就是传说中的跳跃表,不过如此(附:跳跃表的Java实现)的主要内容,如果未能解决你的问题,请参考以下文章

Redis中的跳跃表

跳跃表C语言实现

跳跃表C语言实现

跳跃表C语言实现

redis 系列7 数据结构之跳跃表

高级数据结构 ---- 跳跃表(Skip List)