跳跃列表原理和实现

跳跃列表原理和实现

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

posted @ 2017-07-08 20:30  迪峰  阅读(1914)  评论(0编辑  收藏  举报