资讯专栏INFORMATION COLUMN

认识与实现Skip List

Yangyang / 1326人阅读

摘要:跳表全称叫做跳跃表,简称跳表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

前言

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

1. 跳表的样儿

2. 跳表具有如下性质:

1.由很多层结构组成
2.每一层都是一个有序的链表
3.最底层(第一层)的链表包含所有元素
4.如果一个元素出现在 第 i 层 的链表中,则它在第 i 层 之下的链表也都会出现。
5.每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

3. 来一个栗子,你将豁然开朗

栗子:查找元素 6
1.头指针最开始在第一层最小值INT_MIN处
2.于是和旁边的1比较,当然了1大嘛,于是指针移向1
3.再和当前层4比较, 比 4 大,跳到4
4.与7比较,比7小,于是向下跳到第二层4
5..与5比较,比5大,跳到5
6.与7比较,比7小,跳到最低层5
7.只能依次向后比较了,于是找到了6

4. 笔者的Java实现,慢慢看,很清晰

1.节点定义,共三个成员变量:value,right,down

package crabapple;

/*
 * Copyright (c) This is zhaoxubin"s Java program.
 * Copyright belongs to the crabapple organization.
 * The crabapple organization has all rights to this program.
 * No individual or organization can refer to or reproduce this program without permission.
 * If you need to reprint or quote, please post it to zhaoxubin2016@live.com.
 * You will get a reply within a week,
 *
 */

import java.util.Random;

/**
 * 节点类定义
 */
class Node {

    //值
    public int value = 0;

    //当前层下一个节点
    public Node right;

    //下一层,直连的节点
    public Node down;

    //构造函数
    public Node() {

    }

    public Node(int value) {
        this.value = value;
    }
}

2.跳表类 定义,大家可以清晰的看请代码逻辑结构,该类只暴露insert()和search()两个方法,其它变量及方法均设为私有.

/**
 * 跳表定义
 */
public class SkipTable {

    //表层数
    private int levelCount;
    
    //表的头指针
    private Node firstNode;

    //初始化:层数为1,共前后两个节点,一个最小值,一个最大值
    private void init() {
        levelCount = 1;
        firstNode=new Node();
        firstNode.value = Integer.MIN_VALUE;
        firstNode.right = new Node(Integer.MAX_VALUE);
    }

    public SkipTable() {
        init();
    }

    /**
     * 查找值
     * @param value
     * @return
     */
    public boolean search(int value) {

        Node current = firstNode;
        return toSearch(current, value);
    }

    private boolean toSearch(Node node, int value) {
        if (node.value == value)
            return true;
        else if (node.right!=null&&value >= node.right.value)
            return toSearch(node.right, value);
        else if (node.down != null)
            return toSearch(node.down, value);
        return false;
    }

    
    
    /**
     * 插入值
     * @param value
     * @return
     */
    public boolean insert(int value) {

        //判断是否有这个元素
        if (search(value))
            return false;

        //随机获取一个层数
        int willLevel = updateLevelCount();

        //判断是否添加新层
        if (willLevel > levelCount) {
            Node newFirstNode = new Node();
            addLevel(firstNode, newFirstNode);
            firstNode=newFirstNode;
            levelCount = willLevel;
        }

        //插入新元素
        Node port = firstNode;
        int skipLevel = levelCount - willLevel;

        //迭代到指定层
        while ((skipLevel--) > 0)
            port = port.down;

        //上下层新节点的桥梁
        Node insertNode = null;

        while (port != null) {

            //获取当前层第一个节点指针
            Node curPort = port;

            //迭代到右边的节点值比自己大为止
            while (port.right.value < value)
                port = port.right;

            //准备插入的新节点
            Node curInNode = new Node(value);

            //更新当前节点和前节点指针指向
            curInNode.right = port.right;
            port.right = curInNode;

            //将当前节点引用给上层节点
            if (insertNode != null)
                insertNode.down = curInNode;

            //将新插入的节点指针更新到insertNode,以备在下一层建立指向
            insertNode = curInNode;

            //行头指针向下迭代
            port = port.down;
        }
        return true;

    }

    /**
     * 添加新层
     *
     * @param oldFirst
     * @param newFirst
     */
    private void addLevel(Node oldFirst, Node newFirst) {

        newFirst.value = oldFirst.value;
        newFirst.down = oldFirst;
        if (oldFirst.right != null) {
            Node newRightNode = new Node();
            newFirst.right = newRightNode;
            addLevel(oldFirst.right, newRightNode);
        }
    }

    /**
     * 以一定概率使得获取一个和老层数差值不超过1的新层数
     * @return
     */
    private int updateLevelCount() {

        Random random=new Random();
        int v = random.nextInt(10);
        return v ==0 ? random.nextInt(levelCount) + 2 : random.nextInt(levelCount)+1 ;
    }
}

3.测试类

package crabapple;


public class Main {
    
    public static void main(String[] args) {
        //测试
        SkipTable skipTable=new SkipTable();
        skipTable.insert(1);
        skipTable.insert(2);
        skipTable.insert(3);
        skipTable.insert(4);
        skipTable.insert(5);
        skipTable.insert(6);
        skipTable.insert(7);
        skipTable.insert(8);
        skipTable.insert(9);
        skipTable.insert(10);

        //健壮性边界值测试
        System.out.println(skipTable.search(0));
        System.out.println(skipTable.search(1));
        System.out.println(skipTable.search(2));
        System.out.println(skipTable.search(5));
        System.out.println(skipTable.search(9));
        System.out.println(skipTable.search(10));
        System.out.println(skipTable.search(11));
    }
}

//output:
/**
 * false
 * true
 * true
 * true
 * true
 * true
 * false
 *
 * Process finished with exit code 0
 */
结语

上面的代码,大家是可以直接运行,笔者能力有限,代码也许还有不足,望大家多多指教.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73978.html

相关文章

  • 转 | Java8初体验(二)Stream语法详解

    摘要:第一个函数生成一个新的实例第二个函数接受两个参数,第一个是前面生成的对象,二个是中包含的元素,函数体就是把中的元素加入对象中。 感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.com 上篇文章[Java8初体验(一)lambda表达式语法]()比较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例子中有很多Stream的例子。这些Stream的例子可...

    taoszu 评论0 收藏0
  • 程序员的算法趣题Q39: 反复排序

    摘要:中的统一的终点是全白状态。比如说,的第个数恰好是,它可以由根据题设规则重排而得。上一篇填充白色填充白色下一篇优雅的地址本系列总目录参见程序员的算法趣题详细分析和全解程序员的算法趣题详细分析和全解 目录 1. 问题描述 2. 解题分析 2.1  Naive Approach--正向全量搜索 ...

    gitmilk 评论0 收藏0
  • Java多线程进阶(二五)—— J.U.C之collections框架:ConcurrentSkip

    摘要:同时,也提供了一个基于的实现类,底层基于红黑树设计,是一种有序的。可以看成是并发版本的,但是和不同是,并不是基于红黑树实现的,其底层是一种类似跳表的结构。上述所有构造器都调用了方法方法将一些字段置初始化,然后将指针指向新创建的结点。 showImg(https://segmentfault.com/img/remote/1460000016201159); 本文首发于一世流云专栏:ht...

    huashiou 评论0 收藏0
  • JavaScript DOM2和DOM3——“遍历”的注意要点

    摘要:级遍历和范围模块定义了两个用于辅助完成顺序遍历结构的类型和这两个类型能够基于给定的起点对结构执行深度优先的遍历操作。其中的属性,表示任何遍历方法在上一次遍历中返回的接待你。通过设置这个属性也可以修改遍历继续进行的节点。 DOM2级遍历和范围模块定义了两个用于辅助完成顺序遍历DOM结构的类型:NodeIterator和TreeWalker;这两个类型能够基于给定的起点对DOM结构执行深度...

    antz 评论0 收藏0

发表评论

0条评论

Yangyang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<