跳跃表原理与Java实现

跳跃表总结

解决了有序链表结构查找特定值困难的问题,查找特定值的时间复杂度为O(logn),他是一种可以代替平衡树的数据结构

问题

假如我们要用某种数据结构来维护一组有序的int型数据的集合,并且希望这个数据结构在插入、删除、查找等操作上能够尽可能着快速,那么,你会用什么样的数据结构呢?
数组:
采用二分法可以在0(logn)的时间里查找指定元素,但是插入和删除不友好:查找0(logn),插入和删除0(n)
链表:
查找0(n),但是插入和删除就是0(1)

优化思路

在这里插入图片描述
如果我们现在查找元素为9的节点,需要从头节点遍历8次。
我们增加一些路径加快查找速度
在这里插入图片描述
现在我们遍历5次就查找到了。
在这里插入图片描述
基于这种方法,对于具有n个元素的链表,我们采用logN+1层指针路径的方法,可以实现在0(logn)查找到元素

调表的搜索

在这里插入图片描述
例子:查找元素 117

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

我靠,java程序员表示真难懂!!!

//如果存在x,返回x所在的节点。否则返回x的后继节点
find(x){
   p=top;   //p指向头节点的最高层节点
   while(1){ //不断循环
      while(p->next->key<x) //横向查找满足key>=x的p
         p=p->next; 
      if(p-dowm==null){ //如果
         return p-next;
      }
      p=p->down;
   }
}

在这里插入图片描述
满足redis标准的跳跃表数据结构

public class SkipList{
    Node header,tail;
    int level; //层数最大的节点
    int length; //目前节点的个数
    static class level{
         Node next; //请进指针
         int span; //跨度
    }
    static class Node implements Comparable { //带有不同层高的节点
         Object data=-1;
         level[] levels; //存储此节点锁数的层数。每次创建时都会随机生成一个32内随机大小的level数组
         double score; //按各个节点的分支大小从小到大排列
         Node pre;
         public Node(value,level){
             this.value=value;
             this.level=leve;
         }
         @Override
         public int compareTo(Object o) {
              return this.value > ((Node )o).value ? 1 : -1;
         }
    }
}

调表的插入

先确定元素占据的层数k(采用丢硬币的方式)
然后在level数组中插入相关的元素

int random_level()  
{  
    K = 1;  
  
    while (random(0,1))   //包括0,不包括1
        K++;  
  
    return K;  
}  

跳跃表的性质

  1. 跳跃表由很多层组成,每一层都是一个有序列表
  2. 跳跃表最底层链表包含所有元素
  3. 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
  4. 跳跃表是一种随机化数据结构,通过抛硬币决定层数

对比

  1. 对比二叉查找树
    因为查找查找树的插入、删除、查找也是近似 O(logn) 的时间复杂度。
    不过,二叉查找树是有可能出现一种极端的情况的,就是如果插入的数据刚好一直有序,那么所有节点会偏向某一边。
  2. 红黑树
    红黑树可以说是二叉查找树的一种变形,红黑在查找,插入,删除也是近似O(logn)的时间复杂度

而且红黑树插入,删除结点时,是通过调整结构来保持红黑树的平衡,比起跳跃表直接通过一个随机数来决定跨越几层,在时间复杂度的花销上是要高于跳跃表的

自定义简单跳跃表的实现-区别于redis的有序列表

自定义数据结构

public class SkipList{
    Node header=new Node(-1,16); //跳跃表的头节点
    int levelMax=16; //允许最大层数
    int length; //当前跳表节点的个数
    int levelCount=1; //当前跳跃表的层数,初始化为1
    static class level{ //层节点,相当于hashmap的entry
        Node next;  //指向下一个节点
        int span;  //跨越的层数,也就是levels数组的大小
        public level(){};
        public level(int span){
           this.span=span;
           this.next=new Node(span);
        }
    }
    static class Node implements Comparable { //带有不同层高的节点
         int value=-1; //数据初始化为-1,为了简便为int类型
         level[] levels; //存储此节点的层节点。每次创建时都会随机生成一个32内随机大小的level数组,相当于hashmap的链表
         public Node(value,span){
             this.value=value;
             this.span=span;
             this.levels=new level(span);
         }
         @Override
         public int compareTo(Object o) {
              return this.value > ((Node )o).value ? 1 : -1;
         }
    }

跳跃表查询方法

待优化,找到值还要一致向下遍历,在levels[i]层找到,则在其下面各层肯定存在

 public Node find(int value){
         Node temp=header;
         for(int levelCount-1;i>=0;i--){ //从最高层遍历到最底层-纵向遍历
              while(temp.levels[i].next!=null && temp.levels[i].next.value<value){ //横向遍历,直到值不小于查找值   temp最终存放指定值的前驱
                   temp=temp.levels[i].next;
              }
         }
         //遍历所有层后,判断是否找到              待定:应该上移判断
         if(temp.levels[i].next!=null && temp.levels[i].next.value==value){
             System. out.println( value+ " 查找成功");
             return temp.levels[i].next.value;
         }
         return null;
    }

向跳跃表插入值,不允许重复

 public void insert(int value){
        int level =getLevel();
        Node newNode = newNode(value, level); //创建一个新节点
        Node[] update= newNode[level]; //记录每一层插入位置的前驱节点
        Node temp = head;
        for(int i = level - 1; i >= 0; i--) { //找到每一层插入的前驱节点
            while(temp.levels[i].next != null&& temp.levels[i].next.value< value) {
                temp = temp.levels[i].next;
            }
            update[i] = temp;
        }
        //把插入节点的每一层连接起来
        for( int i = 0; i < level; i++) {
           newNode.levels[i].next = update[i].levels[i].next;
           update[i].levels[i].next = newNode;
        }
        //判断是否需要更新跳跃表的层数
       if(level > levelCount) {
           levelCount = level;
       } 
       size++;
       System. out.println( value+ " 插入成功");
       }
    }
}

在跳跃表删除值

假设删除值一定存在

 public void delete(int value){
     int level; //存储删除节点得层数
     Node temp = head;
     Node[] update= newNode[levelCount]; //此处大小设置为levelCount!!
     //1. 首先找到删除值所有层得前驱位置
     for(int i = levelCount- 1; i >= 0; i--) { //找到每一层插入的前驱节点
          while(temp.levels[i].next != null&& temp.levels[i].next. value< value) {
              temp = temp.levels[i].next;
          }
          update[i] = temp;
     }
     //2. 更新跳跃表得节点数
     if(temp.levels[i].next != null&& temp.levels[i].next. value== value) {
        size--;
     }
     System. out.println( value+ " 删除成功");
     //删除节点后,连接链表
     for(int i=0;i< levelCount;i++) {
        if(update[i].levels[i].next != null&& update[i].levels[i].next.value== value) {
            update[i].levels[i].next = update[i].levels[i].next.levels[i].next;
        }
     }

模拟抛硬币

自己理解,和其他人优点区别

int getLevel() {
	int level = (int)(Math.random() * (this.levelMax+1)); //随机产生[0,1)的数字
	System.out.println( "当前的level = "+ level);
	return level;
}

随机生成1-16的整数

方法1:使用Random类

Random r=new Random();
int a=r.nextInt(16)+1;  //r.nextInt(16)会生成0-15

方法2:使用Math.random()

int num = (int) (Math.random() * 16 + 1); //Math.random()随机生成[0,1)*16=[1,17)  int[1,17)=[1,16]

总结

学习数据结构,不能只学习原理,一定要手动实现一遍
待更新。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值