跳表介绍(翻转课堂)

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

跳表在性能上和红黑树,AVL树不相上下,但是跳表的原理非常简单,目前在Redis和LeveIDB中都有用到

跳表采用随机技术决定元素应该在哪几层,其中的搜索、插入、删除操作的时间均为O(logn),然而,最坏情况下时间复杂性却变成O(n)。相比之下,在一个有序数组或链表中进行插入/删除操作的时间为O(n),最坏情况下为O(n)。

这个是跳表的模型

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。首先在最高级索引上查找最后一个小于当前查找元素的位置,然后再跳到次高级索引继续查找,直到跳到最底层为止,这时候以及十分接近要查找的元素的位置了(如果查找元素存在的话)。由于根据索引可以一次跳过多个元素,所以跳查找的查找速度也就变快了。

(以上截自百度关于跳表的一些介绍)

不过其中的-1也可以换成任意的负无穷的数,其中最下层是最原始的链表,依次往上是一层二层索引,关于跳表的一些操作,接下来详细介绍。

首先介绍跳表的基本组成单位:跳表节点。

跳表节点包含:

元素值

下一个节点

下一层的对应节点

/**
     * 定义跳表节点
     */
    public class SkipNode {
        //元素值
        public int element;
        //下一个节点
        public SkipNode next = null;
        //下一层对应节点
        public SkipNode down = null;

        public SkipNode( int element ) {
            this.element = element;
        }

        public SkipNode(SkipNode node) {
            this.element = node.element;
        }

    }

其次介绍关于跳表的查找操作

1、从最上层的跳表索引的开头开始 
2、假设当前位置为p,将p的下一个节点的值与key比较
    1) p下一个节点的值 <= key  ,p移动到下一个节点的位置  
    2) p下一个节点的值 > key 或者 p的下一个值为null,p移动到下一层相应的节点位置
3.到达底层后,p的右边可能还有元素,则移动直到p节点的值=key或者p的下一个节点为空
    1) 找到p节点
    2) 找不到p节点

/**
     * 查找节点
     * @param key(节点的值)
     * @return 存在时返回最底层对应节点,不存在时返回null
     */
    public SkipNode find(int key) {
        SkipNode p = head;
        while (p != null && p.down != null) {
            if (p.next != null && key >= p.next.element) {
                // 比右边大或相等时向右
                p = p.next;
            } else if (p.next == null || key < p.next.element) {
                // 否则向下
                p = p.down;
            }
        }
        // 到达底层后右边可能还有元素
        while (p.next != null && key >= p.next.element ) {
            p = p.next;
        }
        if( p.element != -1 && key == p.element) {
            return p;
        }
        return null;
    }

然后是跳表的插入操作

1.确认节点未插入
2.获取随机层数
3.随机层数大于使用层数时:添加新的索引层
4.假设当前节点为p,将p移动到节点的随机层数的头节点
5.依次在随机层数以下的节点插入元素并与上一层关联:
    1)向右移动使得p的下一个节点的元素的值>key或者p的下一个值为null
    2)将key相关的node插入p的下一个位置
    3)与上一层的节点oldnode想关联(如果有的话)
    4)p进入下一层,将oldnode的引用指向node
6.跳表大小+1

/**
     * 插入元素节点
     * @param key(元素的值)
     * @return 插入的节点
     */
    public SkipNode insert( int key ) {
        // 节点已存在不插入
        if( find(key) != null ) {
            return null;
        }
        SkipNode node = new SkipNode( key );
        //获取元素随机层数
        int level = getLevel();
        //当随机层数大于使用层数时候添加层数
        if ( level > nodelevel ) {
            addlevel(level - nodelevel);
        }
        SkipNode p = head;
        while (nodelevel - level > 0) {
            p = p.down;
            level++;
        }  // 获得最高更新索引层的头结点
        SkipNode oldnode = null;
        while (p != null) {
            while (p.next != null && p.next.element < key) {
                p = p.next;
            }
            node.next = p.next;
            p.next = node;
            if (oldnode != null) {
                oldnode.down = node;
            }
            oldnode = node;
            if (p.down != null) {
                node = new SkipNode(node);
            }
            p = p.down;
        }
        size++;
        return node;
    }

最后是节点的删除操作

1.首先判断跳表是否为空
2.找到待删除节点的最高层索引的节点的前置节点
3.记录待删除元素
4.p为2中找到的前置节点
    1)将p的下一个元素删除
    2)找到下一层的待删除节点的前置节点
5.删除仅剩头节点的索引层
6.减少跳表的节点数量

/**
     * 删除元素节点
     * @param key(要删除元素的值)
     * @return value(删除元素的值)
     */
    public int remove(int key) {
        // 链表为空,返回-1
        if( head.next == null ) {
            return -1;
        }
        //找到待删除元素的最上层的前一个节点
        SkipNode p = findPreNode(key);
        if (p == null) {
            // 结点不存在
            return -1;
        }
        // 记录待删除元素
        int value = p.next.element;
        while (p != null && p.next != null ) {
            p.next = p.next.next;
            //往下遍历,直到最底下一层
            p = p.down;
            //找到每层待删除元素的前一个节点
            while (p != null && p.next.element != key) {
                p = p.next;
            }
        }
        // 删除仅剩头结点的索引(下面称为空索引)
        p = head;
        // 计算索引层数减少量
        int newlevel = nodelevel;
        // 删除连续空索引
        while (p.down != null && p.next == null) {
            p = p.down;
            newlevel--;
        }
        head = p;
        nodelevel = newlevel;
        //减少跳表元素数量
        size--;
        return value;
    }

测试代码

public static void main(String[] args) {
        SkipList l = new SkipList();
        l.insert(1);
        l.show();
        l.insert(2);
        l.show();
        l.insert(4);
        l.show();
        l.insert(6);
        l.show();
        l.insert(5);
        l.show();
        l.insert(5);
        l.show();
        l.insert(0);
        l.show();
        l.remove(5);
        l.show();
        l.remove(1);
        l.show();
        l.remove(4);
        l.show();
    }

测试结果

-1->1
-1->1
----------------------------
-1->1
-1->1->2
----------------------------
-1->1->4
-1->1->2->4
----------------------------
-1->1->4
-1->1->2->4->6
----------------------------
-1->1->4
-1->1->2->4->5->6
----------------------------
-1->1->4
-1->1->2->4->5->6
----------------------------
-1->1->4
-1->0->1->2->4->5->6
----------------------------
-1->1->4
-1->0->1->2->4->6
----------------------------
-1->4
-1->0->2->4->6
----------------------------
-1->0->2->6
----------------------------

源代码

import java.util.Random;

public class SkipList {

    /**
     * 定义跳表节点
     */
    public class SkipNode {
        //元素值
        public int element;
        //下一个节点
        public SkipNode next = null;
        //下一层对应节点
        public SkipNode down = null;

        public SkipNode( int element ) {
            this.element = element;
        }

        public SkipNode(SkipNode node) {
            this.element = node.element;
        }

    }

    /**
     * 初始跳表的空节点
     */
    public SkipNode head = new SkipNode( -1 );
    /**
     * 跳表的索引层数
     */
    int nodelevel = 0;
    /**
     * 跳表的元素数量
     */
    int size = 0;
    /**
     * 随机数
     */
    Random random = new Random();

    /**
     * 查找节点
     * @param key(节点的值)
     * @return 存在时返回最底层对应节点,不存在时返回null
     */
    public SkipNode find(int key) {
        SkipNode p = head;
        while (p != null && p.down != null) {
            if (p.next != null && key >= p.next.element) {
                // 比右边大或相等时向右
                p = p.next;
            } else if (p.next == null || key < p.next.element) {
                // 否则向下
                p = p.down;
            }
        }
        // 到达底层后右边可能还有元素
        while (p.next != null && key >= p.next.element ) {
            p = p.next;
        }
        if( p.element != -1 && key == p.element) {
            return p;
        }
        return null;
    }

    /**
     * 获得随机索引层数
     * @return 元素的索引层数
     */
    int getLevel() {
        int level = 0;
        for (int i = 0; i <= nodelevel; i++) {
            if (random.nextInt(2) == 1) {
                //每层都有一半概率
                level++;
            } else {
                break;
            }
        }
        return level;
    }

    /**
     * 增加索引层数
     * @param x(要增加的索引层数)
     */
    public void addlevel(int x) {
        for (int i = 0; i < x; i++) {
            // 每层建立一个头结点
            SkipNode newfirst = new SkipNode(-1);
            newfirst.next = null;
            newfirst.down = head;
            head = newfirst;
        }
        nodelevel = x + nodelevel;
    }

    /**
     * 插入元素节点
     * @param key(元素的值)
     * @return 插入的节点
     */
    public SkipNode insert( int key ) {
        // 节点已存在不插入
        if( find(key) != null ) {
            return null;
        }
        SkipNode node = new SkipNode( key );
        //获取元素随机层数
        int level = getLevel();
        //当随机层数大于使用层数时候添加层数
        if ( level > nodelevel ) {
            addlevel(level - nodelevel);
        }
        SkipNode p = head;
        while (nodelevel - level > 0) {
            p = p.down;
            level++;
        }  // 获得最高更新索引层的头结点
        SkipNode oldnode = null;
        while (p != null) {
            while (p.next != null && p.next.element < key) {
                p = p.next;
            }
            node.next = p.next;
            p.next = node;
            if (oldnode != null) {
                oldnode.down = node;
            }
            oldnode = node;
            if (p.down != null) {
                node = new SkipNode(node);
            }
            p = p.down;
        }
        size++;
        return node;
    }

    /**
     * 寻找前置节点
     * @param key(节点的值)
     * @return 指定节点在最高层索引的前置节点
     */
    private SkipNode findPreNode(int key) {
        SkipNode p = head;
        while (p != null && p.down != null) {
            if (p.next != null && key > p.next.element) {
                // 比右边大时向右
                p = p.next;
            } else if (p.next != null && key == p.next.element) {
                // 与右边相等时返回自身
                return p;
            } else if (p.next == null || key < p.next.element) {
                // 否则向下
                p = p.down;
            }
        }
        // 到达底层后右边可能还有元素
        while (p.next != null) {
            if (key == p.next.element) {
                //System.out.println(p.element.value);
                return p;
            }
            p = p.next;
        }
        // 无前置结点
        return null;
    }

    /**
     * 删除元素节点
     * @param key(要删除元素的值)
     * @return value(删除元素的值)
     */
    public int remove(int key) {
        // 链表为空,返回-1
        if( head.next == null ) {
            return -1;
        }
        //找到待删除元素的最上层的前一个节点
        SkipNode p = findPreNode(key);
        if (p == null) {
            // 结点不存在
            return -1;
        }
        // 记录待删除元素
        int value = p.next.element;
        while (p != null && p.next != null ) {
            p.next = p.next.next;
            //往下遍历,直到最底下一层
            p = p.down;
            //找到每层待删除元素的前一个节点
            while (p != null && p.next.element != key) {
                p = p.next;
            }
        }
        // 删除仅剩头结点的索引(下面称为空索引)
        p = head;
        // 计算索引层数减少量
        int newlevel = nodelevel;
        // 删除连续空索引
        while (p.down != null && p.next == null) {
            p = p.down;
            newlevel--;
        }
        head = p;
        nodelevel = newlevel;
        //减少跳表元素数量
        size--;
        return value;
    }

    /**
     * 展示跳表的所有元素
     */
    public void show( ) {
        SkipNode p = head;
        while( p != null ) {
            SkipNode p1 = p;
            while( p1 != null ) {
                System.out.print( p1.element );
                p1 = p1.next;
                if (p1 == null) {
                    continue;
                }
                System.out.print( "->" );
            }
            p = p.down;
            System.out.println();
        }
        System.out.println("----------------------------");
    }

    public static void main(String[] args) {
        SkipList l = new SkipList();
        l.insert(1);
        l.show();
        l.insert(2);
        l.show();
        l.insert(4);
        l.show();
        l.insert(6);
        l.show();
        l.insert(5);
        l.show();
        l.insert(5);
        l.show();
        l.insert(0);
        l.show();
        l.remove(5);
        l.show();
        l.remove(1);
        l.show();
        l.remove(4);
        l.show();
    }
}

跳表用于redis内部的sorted set排序功能。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值