跳跃表 SkipList 原理及Java实现

跳跃表是一种有序数据结构,通过多级指针加速查找,平均查找效率与平衡二叉树相当。本文介绍了跳跃表的定义、查找、插入和删除算法,并提供了Java实现。
摘要由CSDN通过智能技术生成

介绍

在二分查找中,我们需要对数组进行随机访问,而在普通的单链表中,我们只能从头到尾遍历,即使单链表是有序的。
跳跃表是有序的数据结构,在每个节点维持了多个指向其他节点的指针,达到快速访问节点的目的。
跳跃表查找的时间复杂度最坏为O(N),平均O(logN),大部分情况可以和平衡二叉树媲美,而且实现更简单。
本文的实现和数据结构的定义参照了这篇论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》

在这里插入图片描述
如上图:a就是最普通的单链表,在单链表的基础上进行修改,使得某些节点有指向后面其他节点的指针,跳过了单链表的下一个节点,直接指向了后面的节点,就形成的跳跃表。这样的好处是我们可以在高层更快地遍历节点。

数据结构的定义

public class SkipList {
	//节点
    class SkipListNode {
		//向前的指针
        SkipListNode[] forward;
		//键
        Comparable key;
		//数据
        Object value;

        SkipListNode(Comparable key, Object value) {
            this.forward = new SkipListNode[maxLevel];
            this.value = value;
            this.key = key;
        }
    }
	//头节点
    private SkipListNode header;
	//允许的最高层级
    private int maxLevel;
	//当前跳跃表的最高层级
    private int level;
	//有多少个元素
    private int size;
}

跳跃表的节点可以看成有若干层(实际上是同一个节点),最下面是第一层,每一层的指针指向其他节点的同一层,并且有高层则必须有底层。
我们可以这样来得到一个跳跃表:在单链表中,遍历一个元素,让他有1/2的几率得到第二层,再1/2的几率得到的三层。。。。。。层数越高,几率越低,并且不能超过允许的最高层级,再用该节点的每一层的指针指向下一个有该层的节点,没有就直接指向空。这样,层级越高,跨度越大,跳跃越快。头节点拥有每一层级的指针,并且大于当前跳跃表的最高层级节点的层级指针指向空。

算法设计

查找

在查询一个节点的过程中,我们首先访问跳跃表头节点的最高层级,我们看指向的元素是否小于我们要找的元素,如果是,那么就向右移动,直到不小于或者下一个为空为止。如果该层级的下一个元素不为空并且等于要找的元素,那么可以直接返回数据,否则,向下移动一个层级,再重复上述过程,直到找到,或者不能再向下移动了返回空。
在这里插入图片描述

例如:上面图的e,我们找17。

  • 我们在头节点的第四层,下一个是6,小于17,向右移动
  • 在6的第四层,右边是空,向下移动
  • 在6的第三层,右边是25,大于17,向下移动
  • 在6的第二层,右边是9, 小于17,向右移动
  • 在9的第二层,右边是17,返回17的结果

插入

插入的过程中,我们用类似查找的方法找到元素在第一层的应当放置的位置,即左边小,右边大,如果在过程中发现了该元素,那么将新的数据替换掉原来的数据。生成一个新的节点,我们先定下它的层级,拥有第二层的几率是1/2,拥有第三层的几率是1/4。。。。。。另外,插入一个元素我们可能需要改变左边节点的指针的指向(当生成的节点的层级高于该指针时),我们用update数组来记录需要被改变的节点,以及改变哪一层。每一层其实只有一个节点需要改变该层的指针,那么我们用update[i]存放可能要改变的节点,让它的第i+1层指针指向新的节点,如果生成的节点层级大于当前最高层级,我们还要把header放入对应的update位置中,如下图
在这里插入图片描述
如上图所示:我们需要在上面的结构中插入17的节点,我们在寻找的过程中,记录下了update = [12, 9, 6, 6],意思是6的第四层和第三层,9的第二层,和12的第一层可能需要改变指针指向,而我们生成的17只有两层,那么就只改9和12。在生成17节点后,我们将17节点每一层的指针指向update数组对应层的节点的原来的下一个节点,比如第二层update[1] = 节点9,我们就将节点17的第二层指向原来节点9的原本下一个节点L节点25,然后再改变节点9的指向,让它指向17节点。

删除

删除的过程和插入的过程相似,同样用查找的方法来遍历,直到找到或者最后找不到,并且过程中也记录update数组,让其在节点删除后能够指向被删除节点的下一个

代码实现

@SuppressWarnings("unchecked")
public class SkipList {

    class SkipListNode {

        SkipListNode[] forward;

        Comparable key;

        Object value;

        SkipListNode(Comparable key, Object value) {
            this.forward = new SkipListNode[maxLevel];
            this.value = value;
            this.key = key;
        }
    }

    private SkipListNode header;

    private int maxLevel;

    private int level;

    private int size;

    public SkipList() {
        this(4);
    }

    public SkipList(int maxLevel) {
        this.maxLevel = maxLevel;
        this.header = new SkipListNode(null, null);
        this.level = 1;
    }

    public boolean insert(Comparable newKey, Object newValue) {

        SkipListNode current = this.header;
        SkipListNode[] update = new SkipListNode[maxLevel];
        for (int i = this.level-1 ; i >= 0 ; i--) {
            while (current.forward[i] != null && current.forward[i].key.compareTo(newKey) < 0) {
                current = current.forward[i];
            }
            update[i] = current;
            //相同则替换
            if (current.forward[i] != null && current.forward[i].key.compareTo(newKey) == 0) {
                current.forward[i].key = newKey;
                current.forward[i].value = newValue;
                return true;
            }
        }
        int lvl = this.getRandomLevel();
        //大于当前最高层级,需要把更高级的header放入update数组来改变指针指向
        if (lvl > this.level) {
            for (int i = this.level ; i <= lvl-1 ; i++) {
                update[i] = this.header;
            }
            this.level = lvl;
        }
        current = new SkipListNode(newKey, newValue);
        for (int i = 0 ; i < lvl ; i++) {
            current.forward[i] = update[i].forward[i];
            update[i].forward[i] = current;
        }
        this.size++;
        return true;
    }

    private int getRandomLevel() {
        int res = 1;
        while (Math.random() < 0.5 && res < this.maxLevel) {
            res = res+1;
        }
        return res;
    }


    public Object get(Comparable key) {
        SkipListNode current = this.header;
        for (int i = this.level - 1 ; i >= 0 ; i--) {
            while (current.forward[i] != null && current.forward[i].key.compareTo(key) < 0) {
                current = current.forward[i];
            }
            if (current.forward[i] != null && current.forward[i].key.compareTo(key) == 0) {
                return current.forward[i].value;
            }
        }
        return null;
    }


    public boolean remove(Comparable key) {
        SkipListNode current = this.header;
        SkipListNode[] update = new SkipListNode[maxLevel];
        for (int i = this.level - 1 ; i >= 0 ; i--) {
            while (current.forward[i] != null && current.forward[i].key.compareTo(key) < 0) {
                current = current.forward[i];
            }
            update[i] = current;
        }
        current = current.forward[0];
        if (current != null && current.key.compareTo(key) == 0) {
        	//一直从下到上修改指向,直到某一层不是指向要删除的节点
            for (int i = 0 ; i < this.level; i++) {
                if (update[i].forward[i] != current){
                    break;
                }
                update[i].forward[i] = current.forward[i];
            }
			//删除可能导致层级变化
            while (this.level > 1 && this.header.forward[level - 1] == null) {
                this.level--;
            }
            this.size--;
            return true;
        } else {
            return false;
        }
    }


	public int getSize() {
		return this.size;
	}

	//可以用来打印看看跳跃表的结构
    public void printValues() {

        for (int i = this.level - 1 ; i >= 0 ; i--) {
            SkipListNode current = this.header.forward[i];
            while (current != null) {
                System.out.print(current.value+"--->");
                current = current.forward[i];
            }
            System.out.println("NIL");
        }
    }
    
}

测试

 public static void main(String[] args) {
        SkipList list = new SkipList();
        list.insert(1,1);
        list.insert(3,3);
        list.insert(2,2);
        list.insert(7,7);
        list.insert(5,5);
        list.insert(4,4);
        list.printValues();
        System.out.println("result: "+list.get(5));
        list.insert(8,55);
        list.printValues();
        System.out.println("result: "+list.get(8));
        list.remove(8);
        list.printValues();
        System.out.println("result: "+list.get(8));
    }
   
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->NIL
result: 5
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->55--->NIL
result: 55
7--->NIL
7--->NIL
3--->7--->NIL
1--->2--->3--->4--->5--->7--->NIL
result: null
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值