• 跳跃列表原理和实现


    跳跃列表原理和实现

    1.跳跃列表简介:

    跳跃列表是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树。基本上,跳跃列表是对有序的链表增加上附加的前进连接,增加是以随机化的方式进行的,所以在列表中的查找可以快速地跳过部分列表,因此而得名。所有操作都以对数随机化时间进行。
    以上简介摘自-维基百科-跳跃列表

    2.跳跃列表的产生思想

    有序链表大家都熟悉,假如有一个如下的有序链表:

    查找元素23,得从头结点开始依次遍历节点直到找到此节点。这样如果链表很长,而正要查找的元素位于链表比较靠后的位置,则就相当于全部遍历。

    怎么能使查找变快呢?可否跳过中间的一些节点呢?
    基于这样的想法,我们可以大胆假想链表如下结构:

    假如在构造此链表的时候,我们构造了另一个链表,它指向原链表中的元素,只不过它中间跳过一些节点,但任然是按照原链表顺序将其中元素串起来,这样在查找过程中可以跳过一些节点。既然构造一个可以,那多个呢?但是插入,删除元素呢会有什么问题呢?基于这些疑问,接下来我们看看跳跃列表的详细构造和描述。

    3.跳跃表的构造和描述

    跳跃表描述:

    • 一个跳跃列表由几层组成
    • 底层包含所有元素
    • 每一层都是一个有序链表
    • 在层 i 中的元素按某个固定的概率 p (通常为0.5或0.25)出现在层 i+1 中(也就是说高层中的元素必然在低层)
    • 第i层的某个元素可以向下访问与它有相同值的下层节点
      如下所示:
    level 4:1
    level 3:1-----4---6
    level 2:1---3-4---6-----9
    level 1:1-2-3-4-5-6-7-8-9-10
    

    也skiplist的cookbook:

    下来说一下跳跃表的插入,删除,查询的操作
    插入操作步骤:

    • 1.从顶层链表开始遍历,寻找插入点。
      具体:
      插入元素x和当前指向的节点的值y比较:
      x < y:从当前查找位置,下降一层
      x > y:继续向前遍历
      重复上面步骤直到结束。遍历时记录每一层的最后遍历位置,这里用update表示,也就是要插入的位置。

    • 2.申请新节点,将新元素放入,随机产生一个层数。如果层数大于当前跳跃表的层数,扩大1步骤中的层记录,将新层的值修改为头节点。

    • 3.将update记录的指向对应层的指针指向新的节点,新节点的各个层的指针指向后一个节点。(和普通链表的插入时调整指针是类似的,只不过这里是多层调整)

    • 4.修改跳跃列表的层数

    图解:

    图为元素21找到插入位置后,然后将前面的红箭头都指向21,21指向23

    删除操作:

    • 1.从顶层链表开始遍历,寻找删除位置点。
      具体:
      插入元素x和当前指向的节点的值y比较:
      x < y:从当前查找位置,下降一层
      x > y:继续向前遍历
      重复上面步骤直到结束。遍历时记录每一层的最后遍历位置,这里用update表示,也就是要删除的元素。
    • 2.取出最底层的最后遍历位置的元素,与要删除的元素对比,如果相等,则此元素是要删除的元素,与插入类似,将update记录的对应层的指针指修改为删除节点上的对应层上的指针值(和普通链表的删除时调整指针是类似的,只不过这里是多层调整),释放
    • 4.修改跳跃列表的层数。

    4.跳跃表的实现

    节点类:

    public class SkipNode<K,V>{
        K k;
        V v;
        SkipNode<K,V>[] forward; 
        
        @SuppressWarnings("unchecked")
    	public SkipNode(K k,V v,int level) {
        	this.k = k;
        	this.v = v;
        	forward = (SkipNode<K,V>[])new SkipNode[level + 1];
            for(int i = 0;i < level;i++) {
            	forward[i] = null;
            }
        }
        
    	@Override
    	public String toString() {
    		return "SkipNode [k=" + k + ", v=" + v + ", forward=" + Arrays.toString(forward) + "]";
    	}
    }
    

    跳跃列表类:

    public class SkipList<K extends Comparable<? super K>,V> {
    	SkipNode<K,V> head;
    	int level;
    	int size;
    	public SkipList(){
    		head = new SkipNode<K,V>(null,null,0);
    		//刚开始只有一层,也就是第0层
    		level = 0;
    		size = 0;
    	}
    	/**
    	 * @Description:随机生成层数
    	 * @return:int
    	 */
    	private int randomLevel() {
    		int lev;
    		for(lev = 1;Util.random(2) == 0;lev++);
    
    		return lev;
    	}
    
    	public void insert(K k,V v) {
    		int newLevel = randomLevel();
    		//调整头节点
    		if(newLevel > level){
    			SkipNode<K,V> tmp = head;
    			head = new SkipNode<K,V>(null,null,newLevel);
    			for(int i = 0;i < tmp.forward.length;i++){
    				head.forward[i] = tmp.forward[i];
    			}
    			level = newLevel;
    		}
    		@SuppressWarnings("unchecked")
    		SkipNode<K,V>[] update = new SkipNode[level + 1];
    		SkipNode<K,V> x = head;
    		for(int i = level;i >= 0;i--) {
    			while(x.forward[i] != null && x.forward[i].k.compareTo(k) < 0) {
    				x = x.forward[i];
    			}
    			update[i] = x;
    		}
    		x = x.forward[0];
    		if(x != null && x.k != null && x.k.compareTo(k) == 0) {
    			x.v = v;
    		} else {
    			x = new SkipNode<K,V>(k,v,newLevel);
    			for(int i = 0;i < newLevel;i++) {
    				x.forward[i] = update[i].forward[i];
    				update[i].forward[i] = x;
    			}
    			size++;  
    		}
    	}
    
    
    	public V find(K k) {
    		SkipNode<K,V> x = head;
    		for(int i = level;i >= 0;i--) {
    			while(x.forward[i] != null && x.forward[i].k.compareTo(k) < 0) {
    				x = x.forward[i];
    			}
    		}
    		x = x.forward[0];
    		if(k.compareTo(x.k) == 0) {
    			return x.v;
    		}
    		return null;
    	}
    
    
    	public void delete(K k) {
    		@SuppressWarnings("unchecked")
    		SkipNode<K,V>[] update = new SkipNode[level + 1];
    		SkipNode<K,V> x = head;
    		for(int i = level;i >= 0;i--) {
    			while(x.forward[i] != null && x.forward[i].k.compareTo(k) < 0) {
    				x = x.forward[i];
    			}
    			update[i] = x;
    		}
    		x = x.forward[0];
    		if(k.compareTo(x.k) == 0) {
    			for(int i = 0;i < level;i++) {
    				if(update[i].forward[i] != x) {
    					break;
    				}
    				update[i].forward[i] = x.forward[i];
    			}
    			x = null;
    			while(level > 0 && head.forward[level] == null) {
    				level = level - 1;
    			}
    			size--;
    		}
    	}
    	/**
    	 * @Description:按层输出(只输出key)
    	 * @return:void
    	 */
    	public void printKeyByLevel() {
    		SkipNode<K,V>  x = head;
    		for(int i = level - 1;i >= 0;i--) {
    			System.out.print("level-" + i + ":");
    			x = head.forward[0];
    			String headCurLevelForward = head.forward[i] != null?head.forward[i].k.toString() : "NULL";
    			System.out.print(String.format("%5s",headCurLevelForward) + " ");
    			while(x != null) {
    				if(x.forward.length <= i) {
    					System.out.print(String.format("%5s"," ") + " ");
    				} else {
    					if(x.forward[i] == null) {
    						System.out.print(String.format("%5s"," ") + " ");
    					} else {
    						System.out.print(String.format("%5s", x.forward[i].k) + " ");
    					}
    				}
    				x = x.forward[0];
    			}
    			System.out.println();
    		}
    	}
    	/**
    	 * @Description:按节点输出(只输出key)
    	 * @return:void
    	 */
    	public void printKeyByNode() {
    		SkipNode<K,V> x = head;
    		while(x != null) {
    			System.out.print(String.format("%4s forward-size-%s:",x.k == null?"head":x.k,x.forward.length) + " ");
    			for(int i = 0;i < x.forward.length;i++) {
    				if(x.forward[i] == null) {
    					System.out.print(String.format("%3s"," ") + " ");
    				} else {
    					System.out.print(String.format("%3s",x.forward[i].k,x.forward[i].v) + " ");
    				}
    			}
    			System.out.println();
    			x = x.forward[0];
    		}
    	}
    	/**
    	 * @Description: 随机数生成
    	 */
    	static class Util {
    		static Random  random = new Random();
    		public  static int random(int n) {
    			return Math.abs(random.nextInt()) % n;
    		}
    	}
    
    	public static void main(String[] args) {
    		SkipList<Integer,Integer> skipList = new SkipList<Integer, Integer>();
    		skipList.insert(1, 1);
    		skipList.insert(5, 5);
    		skipList.insert(17, 17);
    		skipList.insert(19, 19);
    		skipList.insert(23, 23);
    		skipList.insert(26, 26);
    		skipList.insert(21, 21);
    		System.out.println("printKeyByLevel:");
    		skipList.printKeyByLevel();
    		System.out.println("printKeyByNode:");
    		skipList.printKeyByNode();
    	}
    }
    

    运行结果样例:
    新建跳跃列表,插入元素(1,1),(5,5),(17,17),(19,19),(21,21),(23,23),(26,26)
    由于是随机化的,所以每次结果都会不一样,所以这里只作为参照。

    printKeyByLevel:
    level-4:   17                                           
    level-3:   17                                           
    level-2:   17                23                         
    level-1:    1    17          23                         
    level-0:    1     5    17    19    21    23    26       
    printKeyByNode:
    head forward-size-6:   1   1  17  17  17     
       1 forward-size-3:   5  17     
       5 forward-size-2:  17     
      17 forward-size-6:  19  23  23             
      19 forward-size-2:  21     
      21 forward-size-2:  23     
      23 forward-size-4:  26             
      26 forward-size-2:         
    

    以上已我看着skiplist的cookbook上的算法实现的,这里做了一点调整,就是在插入元素的时候,先随机生成层数,扩大层调整头节点后,再依次查找,寻找插入位置。也就是将扩大层后对头结点调整的动作提前。源码连接
    参看资料:

  • 相关阅读:
    dirname,basename的用法与用途
    终极解决方案——sbt配置阿里镜像源,解决sbt下载慢,dump project structure from sbt耗时问题
    linux-manjaro下添加Yahei Hybrid Consola字体
    Idea无法调出搜狗等中文输入法
    Spring 源码学习系列
    BF算法
    Mybatis Mapper接口是如何找到实现类的-源码分析
    Lua脚本在redis分布式锁场景的运用
    GO语言一行代码实现反向代理
    SpringMVC源码分析-400异常处理流程及解决方法
  • 原文地址:https://www.cnblogs.com/difeng/p/7137918.html
Copyright © 2020-2023  润新知