zoukankan      html  css  js  c++  java
  • 跳表

    为什么选择跳表
    目前经常使用的平衡数据结构像B树,红黑树,AVL树等这些,想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树,还要参考网上的代码,相当麻烦。
    那么在这种情况下,我们就可以用跳表。


    那么什么是跳表
    跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,就能轻松实现一SkipList。

    有序表的搜索
    考虑一个有序表:

    从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数
    为 2 + 4 + 6 = 12 次。因为链表不能用二分查找,二分查找是正对与数组的。那有没有优化的算法吗? 类似二叉搜索树,我们把一些节点提取出来,作为索引。得到如下结构:

    这里我们把 < 14, 34, 50, 72 > 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了。
    我们还可以再从一级索引提取一些元素出来,作为二级索引,变成如下结构:

    这里元素不多,体现不出优势,如果元素足够多,这种索引结构就能体现出优势来了。
    这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。

    下面的结构是就是跳表:
    其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。

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

    例如查找元素 117
    (1) 比较 21, 比 21 大,往后面找
    (2) 比较 37, 比 37大,比链表最大值小,从 37 的下面一层开始找
    (3) 比较 71, 比 71 大,比链表最大值小,从 71 的下面一层开始找
    (4) 比较 85, 比 85 大,从后面找
    (5) 比较 117, 等于 117, 找到了节点。

    java实现

    import java.util.Random;
    
    public class SkipList {
    private static Node head; // 头结点
    private static Node tail; // 尾结点
    private static int level; // 层数
    private static int size; // 节点个数
    private static Random random; // 随机数来确定层数
    
    public SkipList() {
    head = new Node(Integer.MIN_VALUE); // 头结点为最小值
    tail = new Node(Integer.MAX_VALUE); // 尾结点为最大值
    // 连起来
    head.setRight(tail);
    tail.setLeft(head);
    level = 0; // 初始化层数为0
    size = 0; // 初始化节点个数为0
    random = new Random(); // new出random对象
    }
    
    // 判断是否为空
    public static boolean isEmpty() {
    if (size == 0) {
    return true;
    }
    return true;
    }
    
    // 查找到需要插入的前一个节点
    public static Node findPrevNode(Integer data) {
    Node node = head;
    while (node != null) {
    // node的头结点不为尾结点并且node的值不大于data的时候,就一直往右走,否则循环结束
    while (node.getRight() != tail && node.getRight().getValue() <= data) {
    node = node.getRight();
    }
    // 如果当前节点有下节点,就继续往下走
    if (node.getDown() != null) {
    node = node.getDown();
    } else {
    // 否则返回当前节点
    return node;
    }
    }
    return null;
    }
    
    // 查找节点
    public static Node findNode(Integer data) {
    Node node = findPrevNode(data);
    if (node == null) {
    return null;
    }
    if (node.getValue() == data) {
    return node;
    }
    return null;
    }
    
    public static void add(Integer data) {
    // 找到前一个节点
    Node prev = findPrevNode(data);
    if (prev == null) {
    return;
    }
    if (data == prev.getValue()) {
    System.out.println("插入重复");
    return;
    }
    // 插入
    Node addNode = new Node(data);
    addNode.setLeft(prev);
    addNode.setRight(prev.getRight());
    prev.getRight().setLeft(addNode);
    prev.setRight(addNode);
    int high = 0;// 当前层数
    while (random.nextDouble() < 0.5) { // 进行随机,是否需要往上层添加
    if (high >= level) { // 如果当前层数超出了高度,就需要新建一层
    Node node1 = new Node(Integer.MIN_VALUE);
    Node node2 = new Node(Integer.MAX_VALUE);
    node1.setRight(node2);
    node1.setDown(head);
    node2.setLeft(node1);
    node2.setDown(tail);
    head.setUp(node1);
    tail.setUp(node2);
    level++;
    head = node1;
    tail = node2;
    }
    // 找到最上一层
    while (prev.getUp() == null) {
    prev = prev.getLeft();
    }
    prev = prev.getUp();
    Node node = new Node(data);
    node.setLeft(prev);
    node.setRight(prev.getRight());
    node.setDown(addNode);
    prev.getRight().setLeft(node);
    prev.setRight(node);
    addNode.setUp(node);
    addNode = node;
    high++; // 高度+1
    }
    size++; // 节点个数+1
    }
    
    // 删除节点
    public static boolean remove(Integer data) {
    Node node = findNode(data);
    if (node == null) {
    return false;
    }
    while (node != null) {
    node.getLeft().setRight(node.getRight());
    node.getRight().setLeft(node.getLeft());
    node = node.getUp();
    }
    return true;
    }
    
    public static void print() {
    Node node;
    Node node1 = head;
    while (node1 != null) {
    int k = 0;
    node = node1;
    while (node != null) {
    System.out.print(node.getValue() + "	");
    k++;
    node = node.getRight();
    }
    
    System.out.print("	");
    System.out.print("(" + k + ")");
    System.out.println();
    node1 = node1.getDown();
    
    }
    }
    
    public static void main(String[] args) {
    SkipList sl = new SkipList();
    for (int i = 0; i < 30; i++) { // 随机数字进行测试
    add(i);
    }
    print();
    System.out.println("----------");
    if (findNode(22) != null) { // 查找
    System.out.println("OK");
    } else {// 找不到
    System.out.println("false");
    
    }
    remove(0); // 删除
    print();
    
    }
    }
    
    // 定义节点
    class Node {
    private Integer value;
    private Node up, down, left, right;
    
    public Node(Integer value) {
    this.value = value;
    }
    
    public Integer getValue() {
    return value;
    }
    
    public void setValue(Integer value) {
    this.value = value;
    }
    
    public Node getUp() {
    return up;
    }
    
    public void setUp(Node up) {
    this.up = up;
    }
    
    public Node getDown() {
    return down;
    }
    
    public void setDown(Node down) {
    this.down = down;
    }
    
    public Node getLeft() {
    return left;
    }
    
    public void setLeft(Node left) {
    this.left = left;
    }
    
    public Node getRight() {
    return right;
    }
    
    public void setRight(Node right) {
    this.right = right;
    }
    
    }
    View Code

    结果

     

  • 相关阅读:
    Spring Controller参数为空串的处理方式
    netstat用法
    zookeeper的配置项
    C++ Lambda表达式用法
    java命令行运行jar里的main类
    Random的nextInt用法
    【JAVA】删除某个目录及目录下的所有子目录和文件
    Centos7设置keepAlived开机自启动
    linux设置nginx开机自启动
    window.open()方法
  • 原文地址:https://www.cnblogs.com/ericz2j/p/11269850.html
Copyright © 2011-2022 走看看