文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

详解Java中跳跃表的原理和实现

2022-12-27 12:00

关注

一、跳跃表的引入

对有序顺序表可以采用二分查找,查找的时间复杂度为O (logn),插入、删除的时间复杂度为 O(n )。但是对有序链表不可以采用二分查找,查找、插入和删除的时间复杂度均为O (n)。

有序链表如下图所示,若查找 8,则必须从第 1 个节点开始,依次比较 8 次才能查找成功。

如何利用链表的有序性来提高查找效率?如何在一个有序链表中进行二分查找?若增加一级索引,把奇数位序作为索引,则如下图所示,若查找 8,则可以先从索引进行比较,依次比较 1、3、5、7、9,8 比 7 大但比 9 小,7 向下一层,继续向后比较,比较 6 次即可查找成功。

若再增加一级索引,把索引层的奇数位序作为索引,则如下图所示,若查找 7,则可以先从索引开始比较,依次 1、5、9,7 比 5 大但比 9 小,5 向下一层,继续向后比较,比较 4 次即可查找成功。

在增加两级索引后,若查找 5,则比较两次即可查找成功;若查找比 5 大的数,则以 5 为界向后查找;若查找比 5 小的数,则以 5 为界向前查找。这就是一个可进行二分查找的有序链表。

二、算法分析

若有 n 个元素,则增加 h 级索引后的数据结构如下图所示。

1.时间复杂度

底层包含所有元素(n个),即 2^(h +1)=n ,索引层数 h=logn-1。搜索时,首先在顶层索引中进行查找,然后二分搜索,最多从顶层搜索到底层,最多 O(logn) 层,因此查找的时间复杂度为 O(logn)。

2.空间复杂度

增加索引需要一些辅助空间,那么索引一共有多少个节点呢?从上图中可以看出,每层索引的节点之和都为 2+2^2 +…+2^h =2^(h +1)-2=n-2,因此空间复杂度为 O(n )。实际上,索引节点并不是原节点的复制,只是附加了一些指针建立索引。以上正是跳跃表的实现原理。

三、跳跃表介绍

跳跃表(Skip list)是有序链表的扩展,简称跳表,它在原有的有序链表上增加了多级索引,通过索引来实现快速查找,实质上是一种可以进行二分查找的有序链表。

实际上,跳跃表并不是简单地通过奇偶次序建立索引的,而是通过随机技术实现的,因此也可以说它是一种随机化的数据结构。假如跳跃表每一层的晋升概率都是 1/2,则最理想的索引就是在原始链表中每隔一个元素抽取一个元素作为一级索引。其实在原始链表中随机选择 n/2 个元素作为一级索引也可以,因为随机选择的元素相对均匀,对查找效率来讲影响不大。当原始链表中元素数量足够大且抽取足够随机时,得到的索引是均匀的。因此随机选择 n/2 个元素作为一级索引,随机选择 n/4 个元素作为二级索引,随机选择 n/8 个元素作为三级索引,以此类推,一直到顶层索引。我们可以通过索引来提升跳跃表的查找效率。

跳跃表不仅可以提高搜索性能,还可以提高插入和删除操作的性能。平衡二叉查找树在进行插入、删除操作后需要多次调整平衡,而跳跃表完全依靠随机技术,其性能和平衡二叉查找树不相上下,但是原理非常简单。跳跃表是一种性能比较优秀的动态数据结构,Redis 中的有序集合 Sorted Set 和 LevelDB 中的 MemTable 都是采用跳跃表实现的。

四、跳跃表的实现

1.数据结构定义

在每个节点都可以设置向右、向下指针,当然,也可以附加向左、向上指针,构建四联表。通过四联表可以快速地在四个方向访问前驱和后继。在此仅设置向右指针,在每个节点都定义一个后继指针数组,通过层次控制向下访问。

2.查找

在跳跃表中查找元素 x ,需要从最上层索引开始逐层查找,算法步骤如下。

(1)从最上层 Sh 的头节点开始。

(2)假设当前位置为 p ,p 的后继节点的值为 y ,若 x=y,则查找成功;若 x>y ,则 p 向后移一个位置,继续查找;若 x<y ,则 p 向下移动一个位置,继续查找。

(3)若到达底层还要向下移动,则查找失败。

例如,跳跃表如下图所示,在表中查找元素 36,则先从顶层的头节点开始,比 20 大,向后为空,p 向下移动到第 2 层;比下一个元素 50 小,p 向下移动到第 1 层;比下一个元素 30 大,p 向右移动;比下一个元素 50 小,p 向下移动到第 0 层;与下一个元素 36 比较,查找成功。

3.插入

在跳跃表中插入一个元素时,相当于在某一个位置插入一个高度为 level 的列。插入的位置可以通过查找确定,而插入列的高度可以采用随机化决策确定。

随机化获取插入元素的层次:

① 初始时 lay=0,可设定晋升概率P为 0.5 或 0.25。

② 利用随机函数产生 0~1的随机数 r。

④ 若 r 小于 P 且 lay 小于最大层次,则 lay+1;否则返回 lay,作为新插入元素的高度。

在跳跃表中插入元素的算法步骤如下。

(1)查找插入位置,在查找过程中用 updata[i] 记录经过的每一层的最大节点位置。

(2)采用随机化策略得到插入层次 lay。

(3)创建新节点,将高度为 lay 的列插入 updata[i] 之后。

例如,跳跃表如下图所示,在表中插入元素 32。首先在跳跃表中查找 32,然后确定插入位置。在查找过程中用 updata[i] 记录经过的每一层的最大节点位置;假设随机化得到的层次为 2,则 i 为 0~2,将新节点插入 updata[i] 之后。

4.删除

在跳跃表中删除一个元素,相当于删除这个元素所在的列。

算法步骤:

(1)查找该元素,在查找过程中用 updata[i] 记录经过的每一层的最大节点位置,实际上是待删除节点在每一层的前一个元素的位置。若查找失败,则退出。

(2)利用 updata[i] 将该元素整列删除。

(3)若有多余空链,则删除空链。

例如,跳跃表如下图所示,在表中删除元素 20。首先在跳跃表中查找 20,在查找过程中用 updata[i] 记录经过的每一层的最大节点位置;然后利用 updata[i] 将每层的 20 节点删除。

删除 20 所在的列后,最上层的链为空链,则删除空链,跳跃表的层次减 1。

五、实战

1.代码

package com.platform;
 
import java.util.Scanner;
 
public class SkipList {
    private static int INF = 0x7fffffff;
    private static float P = 0.5f;
    private static int MAX_LEVEL = 16;
 
    static class Node {
        int val;
        Node forward[] = new Node[MAX_LEVEL];
    }
 
 
    Node head = new Node();
    Node updata[] = new Node[MAX_LEVEL];
 
    public SkipList() {
        for (int i = 0; i < updata.length; i++) {
            updata[i] = new Node();
        }
    }
 
    void Init() {
        level = 0;
        head = new Node();
        for (int i = 0; i < MAX_LEVEL; i++)
            head.forward[i] = null;
        head.val = -INF;
    }
 
    // 随机产生插入元素高度
    static int RandomLevel() {
        int lay = 0;
        while ((float) Math.random() < P && lay < MAX_LEVEL - 1)
            lay++;
        return lay;
    }
 
    Node Find(int val) {//查找最接近val的元素
        Node p = head;
        for (int i = level; i >= 0; i--) {
            while (p.forward[i] != null && p.forward[i].val < val)
                p = p.forward[i];
            updata[i] = p;//记录搜索过程中各级走过的最大节点位置
        }
        return p;
    }
 
    void Insert(int val) {
        Node p, s;
        int lay = RandomLevel();
        System.out.printf("lay=%d\n", lay);
        if (lay > level) //要插入的层 > 现有层数
            level = lay;
        p = Find(val); //查询
        s = new Node();//创建一个新节点
        s.val = val;
        for (int i = 0; i < MAX_LEVEL; i++)
            s.forward[i] = null;
        for (int i = 0; i <= lay; i++) {//插入操作
            s.forward[i] = updata[i].forward[i];
            updata[i].forward[i] = s;
        }
    }
 
    void Delete(int val) {
        Node p = Find(val);
        if (p.forward[0] != null && p.forward[0].val == val) {
            System.out.printf("%d\n", p.forward[0].val);
            for (int i = level; i >= 0; i--) { // 删除操作
                if (updata[i].forward[i] != null && updata[i].forward[i].val == val)
                    updata[i].forward[i] = updata[i].forward[i].forward[i];
            }
            while (level > 0 && head.forward[level] == null) // 删除空链
                level--;
        }
    }
 
    void print(int i) { // 遍历
        Node p = head.forward[i];
        if (p == null) return;
        while (p != null) {
            System.out.printf("%d  ", p.val);
            p = p.forward[i];
        }
        System.out.printf("\n");
    }
 
    void printall() { // 遍历所有层
        for (int i = level; i >= 0; i--) {
            System.out.printf("level %d:  ", i);
            print(i);
        }
    }
 
    void show() {
        System.out.printf("Please select:\n");
        System.out.printf("-----------------------------\n");
        System.out.printf("     1:  插入\n");
        System.out.printf("     2:  删除\n");
        System.out.printf("     3:  查找\n");
        System.out.printf("     4:  遍历\n");
        System.out.printf("     5:  遍历所有层\n");
        System.out.printf("     0:  退出\n");
        System.out.printf("-----------------------------\n");
    }
 
    int level;
 
    public static void main(String[] args) {
        Scanner sn = new Scanner(System.in);
        SkipList skipList = new SkipList();
        skipList.Init();
        int n, x;
        skipList.show();
        while (true){
            n = sn.nextInt();
            switch (n) {
                case 1:
                    x = sn.nextInt();
                    skipList.Insert(x);
                    break;
                case 2:
                    x = sn.nextInt();
                    skipList.Delete(x);
                    break;
                case 3:
                    x = sn.nextInt();
                    Node p;
                    p = skipList.Find(x);
                    if (p.forward[0] != null && p.forward[0].val == x) {
                        System.out.printf("find success!\n");
                    } else {
                        System.out.printf("find fail!\n");
                    }
 
 
                    break;
                case 4:
                    skipList.print(0);
                    break;
                case 5:
                    skipList.printall();
                    break;
            }
            skipList.show();
        }
    }
}

2.测试结果

Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
1
1
lay=0
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
5
level 0:  1  
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
1
4
lay=5
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
5
level 5:  4  
level 4:  4  
level 3:  4  
level 2:  4  
level 1:  4  
level 0:  1  4  
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
1
7
lay=1
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
5
level 5:  4  
level 4:  4  
level 3:  4  
level 2:  4  
level 1:  4  7  
level 0:  1  4  7  
Please select:
----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
1
2
lay=0
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
5
level 5:  4  
level 4:  4  
level 3:  4  
level 2:  4  
level 1:  4  7  
level 0:  1  2  4  7  
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
3
3
find fail!
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
3
2
find success!
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
2
2
2
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------
5
level 5:  4  
level 4:  4  
level 3:  4  
level 2:  4  
level 1:  4  7  
level 0:  1  4  7  
Please select:
-----------------------------
     1:  插入
     2:  删除
     3:  查找
     4:  遍历
     5:  遍历所有层
     0:  退出
-----------------------------

到此这篇关于详解Java中跳跃表的原理和实现的文章就介绍到这了,更多相关Java跳跃表内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯