跳表介绍和实现

想慢慢的给大家自然的引入跳表。

 

想想,我们

1)在有序数列里搜索一个数

2)或者把一个数插入到正确的位置

都怎么做?

很简单吧

对于第一个操作,我们可以一个一个比较,在数组中我们可以二分,这样比链表快

对于第二个操作,二分也没什么用,因为找到位置还要在数组中一个一个挪位置,时间复杂度依旧是o(n)。

那我们怎么发明一个查找插入都比较快的结构呢?

 

 

 

可以打一些标记:

这样我们把标记连起来,搜索一个数时先从标记开始搜起下一个标记比本身大的话就往下走,因为再往前就肯定不符合要求了。

比如我们要搜索18:

因为一次可以跨越好多数呀,自然快了一些。

既然可以打标记,我们可以改进一下,选出一些数来再打一层标记:

这样我们搜索20是这样的:

最终我们可以打好多层标记,我们从最高层开始搜索,一次可以跳过大量的数(依旧是右边大了就往下走)。

比如搜索26:

最好的情况,就是每一层的标记都减少一半,这样到了顶层往下搜索,其实和二分就没什么两样,我们最底层用链表串起来,插入一个元素也不需要移动元素,所谓跳表就完成了一大半了。

 

现在的问题是,我们对于一个新数,到底应该给它打几层标记呢?

(刚开始一个数都没有,所以解决了这个问题,我们一直用这个策略更新即可)

答案是。。。。。投硬币,全看脸。

我其实有点惊讶,我以为会有某些很强的和数学相关的算法,可以保证一个很好的搜索效率,是我想多了。

我们对于一个新数字,有一半概率可以打一层标记,有一半概率不可以打。

对于打了一层标记的数,我们依旧是这个方法,它有一半概率再向上打一层标记,依次循环。

所以每一层能到达的概率都少一半。

各层的节点数量竟然就可以比较好的维护在很好的效率上(最完美的就是达到了二分的效果)

 

再分析一下,其实对于同一个数字:

等等。。

其实没必要全都用指针,因为我们知道,通过指针找到一个数可比下标慢多了。

所以同一个数字的所有标记,没必要再用指针,效率低还不好维护,用一个list保存即可。

这样,我们就设计出来一个数字的所有标记组成的结构:

	public static class SkipListNode {public Integer value;//本身的值public ArrayList<SkipListNode> nextNodes;
//指向下一个元素的结点组成的数组,长度全看脸。public SkipListNode(Integer value) {this.value = value;nextNodes = new ArrayList<SkipListNode>();}}

将integer比较的操作封装一下:

		private boolean lessThan(Integer a, Integer b) {return a.compareTo(b) < 0;}private boolean equalTo(Integer a, Integer b) {return a.compareTo(b) == 0;}

找到在本层应该往下拐的结点:

		// Returns the node at a given level with highest value less than eprivate SkipListNode findNext(Integer e, SkipListNode current, int level) {SkipListNode next = current.nextNodes.get(level);while (next != null) {Integer value = next.value;if (lessThan(e, value)) { // e < valuebreak;}current = next;next = current.nextNodes.get(level);}return current;}

这样我们就写一个一层层往下找的方法,并且封装成find(Integer e)的形式:

		// Returns the skiplist node with greatest value <= eprivate SkipListNode find(Integer e) {return find(e, head, maxLevel);}// Returns the skiplist node with greatest value <= e// Starts at node start and levelprivate SkipListNode find(Integer e, SkipListNode current, int level) {do {current = findNext(e, current, level);} while (level-- > 0);return current;}

刚才的方法是找到最大的小于等于目标的值,如果找到的值等于目标,跳表中就存在这个目标。否则不存在。

		public boolean contains(Integer value) {SkipListNode node = find(value);return node != null && node.value != null && equalTo(node.value, value);}

我们现在可以实现加入一个新点了,要注意把每层的标记打好:

		public void add(Integer newValue) {if (!contains(newValue)) {size++;int level = 0;while (Math.random() < PROBABILITY) {level++;//能有几层全看脸}while (level > maxLevel) {//大于当前最大层数head.nextNodes.add(null);//直接连系统最大maxLevel++;}SkipListNode newNode = new SkipListNode(newValue);SkipListNode current = head;//前一个结点,也就是说目标应插current之后do {//每一层往下走之前就可以设置这一层的标记了,就是链表插入一个新节点current = findNext(newValue, current, level);newNode.nextNodes.add(0, current.nextNodes.get(level));current.nextNodes.set(level, newNode);} while (level-- > 0);}}

删除也是一样的

		public void delete(Integer deleteValue) {if (contains(deleteValue)) {SkipListNode deleteNode = find(deleteValue);size--;int level = maxLevel;SkipListNode current = head;do {//就是一个链表删除节点的操作current = findNext(deleteNode.value, current, level);if (deleteNode.nextNodes.size() > level) {current.nextNodes.set(level, deleteNode.nextNodes.get(level));}} while (level-- > 0);}}

作为一个容器,Iterator那是必须有的吧,里面肯定有hasNext和next吧?

	public static class SkipListIterator implements Iterator<Integer> {SkipList list;SkipListNode current;public SkipListIterator(SkipList list) {this.list = list;this.current = list.getHead();}public boolean hasNext() {return current.nextNodes.get(0) != null;}public Integer next() {current = current.nextNodes.get(0);return current.value;}}

这个跳表我们就实现完了。

现实工作中呢,我们一般不会让它到无限多层,万一有一个数它人气爆炸随机数冲到了一万层呢?

所以包括redis在内的一些跳表实现,都是规定了一个最大层数的。

别的好像也没什么了。

最后贴出所有代码。

import java.util.ArrayList;
import java.util.Iterator;public SkipListDemo {public static class SkipListNode {public Integer value;public ArrayList<SkipListNode> nextNodes;public SkipListNode(Integer value) {this.value = value;nextNodes = new ArrayList<SkipListNode>();}}public static class SkipListIterator implements Iterator<Integer> {SkipList list;SkipListNode current;public SkipListIterator(SkipList list) {this.list = list;this.current = list.getHead();}public boolean hasNext() {return current.nextNodes.get(0) != null;}public Integer next() {current = current.nextNodes.get(0);return current.value;}}public static class SkipList {private SkipListNode head;private int maxLevel;private int size;private static final double PROBABILITY = 0.5;public SkipList() {size = 0;maxLevel = 0;head = new SkipListNode(null);head.nextNodes.add(null);}public SkipListNode getHead() {return head;}public void add(Integer newValue) {if (!contains(newValue)) {size++;int level = 0;while (Math.random() < PROBABILITY) {level++;}while (level > maxLevel) {head.nextNodes.add(null);maxLevel++;}SkipListNode newNode = new SkipListNode(newValue);SkipListNode current = head;do {current = findNext(newValue, current, level);newNode.nextNodes.add(0, current.nextNodes.get(level));current.nextNodes.set(level, newNode);} while (level-- > 0);}}public void delete(Integer deleteValue) {if (contains(deleteValue)) {SkipListNode deleteNode = find(deleteValue);size--;int level = maxLevel;SkipListNode current = head;do {current = findNext(deleteNode.value, current, level);if (deleteNode.nextNodes.size() > level) {current.nextNodes.set(level, deleteNode.nextNodes.get(level));}} while (level-- > 0);}}// Returns the skiplist node with greatest value <= eprivate SkipListNode find(Integer e) {return find(e, head, maxLevel);}// Returns the skiplist node with greatest value <= e// Starts at node start and levelprivate SkipListNode find(Integer e, SkipListNode current, int level) {do {current = findNext(e, current, level);} while (level-- > 0);return current;}// Returns the node at a given level with highest value less than eprivate SkipListNode findNext(Integer e, SkipListNode current, int level) {SkipListNode next = current.nextNodes.get(level);while (next != null) {Integer value = next.value;if (lessThan(e, value)) { // e < valuebreak;}current = next;next = current.nextNodes.get(level);}return current;}public int size() {return size;}public boolean contains(Integer value) {SkipListNode node = find(value);return node != null && node.value != null && equalTo(node.value, value);}public Iterator<Integer> iterator() {return new SkipListIterator(this);}/******************************************************************************* Utility Functions *******************************************************************************/private boolean lessThan(Integer a, Integer b) {return a.compareTo(b) < 0;}private boolean equalTo(Integer a, Integer b) {return a.compareTo(b) == 0;}}public static void main(String[] args) {}}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445409.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习知识总结系列- 基本概念(1-0)

文章目录目录1. 机器学习的定义2. 机器学习的分类2.1根据是否在人类监督下进行训练监督学习非监督学习半监督学习强化学习2.2根据是否可以动态渐进的学习在线学习批量学习2.3根据是否在训练数据过程中进行模式识别实例学习基于模型的学习3. 机器学习中的一些常见名词4. 机器学习…

剑指offer(刷题21-30)--c++,Python版本

文章目录目录第 21题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第22 题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第23 题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第24 题&#xff1a;解题思路&#xff1a;代码实现…

剑指offer(刷题41-50)--c++,Python版本

文章目录目录第41题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第42题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第43题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第44题&#xff1a;解题思路&#xff1a;代码实现&am…

redis——持久化

因为redis是内存数据库&#xff0c;他把数据都存在内存里&#xff0c;所以要想办法实现持久化功能。 RDB RDB持久化可以手动执行&#xff0c;也可以配置定期执行&#xff0c;可以把某个时间的数据状态保存到RDB文件中&#xff0c;反之&#xff0c;我们可以用RDB文件还原数据库…

剑指offer(刷题51-60)--c++,Python版本

文章目录目录第51题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第52题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第53题&#xff1a;解题思路&#xff1a;代码实现&#xff1a;cpython第54题&#xff1a;解题思路&#xff1a;代码实现&am…

2017第一届河北省大学生程序设计竞赛题解

超级密码 小明今年9岁了&#xff0c;最近迷上了设计密码&#xff01;今天&#xff0c;他又设计了一套他认为很复杂的密码&#xff0c;并且称之为“超级密码”. 说实话&#xff0c;这套所谓的“超级密码”其实并不难&#xff1a;对于一个给定的字符串&#xff0c;你只要提取其中…

大数的四则运算(加法、减法、乘法、除法)

大数的四则运算&#xff08;加法、减法、乘法、除法&#xff09; 前言&#xff1a; 在计算机中数字表示的范围是有限制的&#xff0c;比如我们熟知的 int、float、double 等数据类型所能表示的范围都是有限的&#xff0c;如果我们要对位数达到几十位、几百位、上千位的大整数进…

随机过程1

随机过程1概述1.参考书目2.主要内容3.概率论--基本概念回顾3.1对“不确定性”的认识3.2 应对“不确定性”应该怎么做3.3随机变量&#xff08;Random Variable&#xff09;3.4分布函数&#xff08;Distribution Function&#xff09;3.5概率密度&#xff08;Density&#xff09;…

数组基操三连(4)

题目一 给定一个长度为N的整型数组arr&#xff0c;其中有N个互不相等的自然数1~N 请实现arr的排序 但是不要把下标0~N-1位置上的数值通过直接赋值的方式替换成1~N。 要求&#xff1a;时间复杂度为O(N)&#xff0c;额外空间复杂度为O(1)。 思路&#xff1a;从左向右检查&…

Linux(1)-touch,mkdir,rm,mv,cp,ls,cd,cat

Linux1-实用终端命令1. touch, mkdir2. rm, mv, cp3. ls(通配符),cd(绝对/相对路径)4. cat, more/less文件内容浏览文件/目录-增删查改, 文件内容查看.1. touch, mkdir touch新文件 &#xff1a;在当前文件夹下&#xff0c;创建文件。文件不存在则创建新文件&#xff1b;文件存…

数组精选题目三连(5)

子数组的最大累加和问题 输入一个整形数组&#xff0c;求数组中连续的子数组使其和最大。比如&#xff0c;数组x 应该返回 x[2..6]的和187. 这四个代码完成的功能都是求最大子数组&#xff08;注意用词准确&#xff0c;子数组连续&#xff0c;子序列可以不连续&#xff09;。…

大数据学习(1)-大数据概述

文章目录目录大数据产生背景大数据概念大数据影响大数据应用大数据关键技术大数据产业大数据&#xff0c;云计算&#xff0c;物联网关系云计算物联网大数据&#xff0c;物联网&#xff0c;云计算三者之间联系目录 大数据产生背景 三次信息化浪潮 根据IBM前首席执行官郭士纳福…

redis——事件

redis服务器是一个事件驱动程序。 需要处理两类事件&#xff1a; 1&#xff09;文件事件&#xff1a;redis是通过套接字与客户端或者其他服务器连接的&#xff0c;而文件事件就是服务器对套接字操作的抽象。 2&#xff09;时间事件&#xff1a;服务器对一些定时操作的抽象。…

redis——客户端

redis服务器是典型的一对多服务器&#xff0c;通过使用由IO多路复用技术实现的文件事件处理器&#xff0c;redis服务器使用了单线程单进程的方式来处理请求。 客户端的属性 描述符 客户端状态的 fd 属性记录了客户端正在使用的套接字描述符&#xff1a; typedef struct red…

大数据学习(2-1)-Hadoop安装教程-单机模式和伪分布模式(Ubuntu14.04LTS)

文章目录目录1.linxu的安装1.1安装Linux虚拟机1.2安装Linux和Windows双系统2.Hadoop的安装2.1 Hadoop安装前配置2.1.1 配置Hadoop用户2.1.2 安装 ssh , 配置ssh免密登录2.1.3 安装java环境2.2 Hadoop的安装3.Hadoop单机版配置4.Hadoop伪分布版配置目录 1.linxu的安装 1.1安装…

mysql——JDBC

概述 JDBC&#xff1a;java Data Base Connectivity ,java数据库连接&#xff0c;它是一种用于执行sql语句的java API&#xff0c;为多种关系数据库提供统一访问。 其实就是一组用java编写的类和接口。 JDBC API 提供两类主要接口&#xff1a; 1&#xff09;面向开发人员的…