热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

跳跃表mysql_跳跃表原理与实践

---恢复内容开始---像是redis中有序集合就使用到了跳跃表。场景:商品总数量有几十万件,对应数据库商品表的几十万条记录。需要根据不同字段正序或者倒叙

---恢复内容开始---

像是redis中有序集合就使用到了跳跃表。

场景:商品总数量有几十万件,对应数据库商品表的几十万条记录。需要根据不同字段正序或者倒叙做精确或全量查询,而且性能有硬性要求。

如果是按照商品名称精确查询还好办,可以直接从数据库查出来,最多也就上百条记录。

如果是没有商品名称的全量查询怎么办?总不可能把数据库里的所有记录全查出来吧,而且还要支持不同字段的排序。

所以,只能提前在内存中存储有序的全量商品集合,每一种排序方式都保存成独立的集合,每次请求的时候按照请求的排序种类,返回对应的集合。

比如按价格字段排序的集合:

比如按销量字段排序的集合:

商品列表是线性的,最容易表达线性结构的自然是数组和链表。可是,无论是数组还是链表,在插入新商品的时候,都会存在性能问题。

按照商品等级排序的集合为例,如果使用数组,插入新商品的方式如下:

9206589.html

如果要插入一个价格是3的商品,首先要知道这个商品应该插入的位置。使用二分查找可以最快定位,这一步时间复杂度是O(logN)。

插入过程中,原数组中所有大于3的商品都要右移,这一步时间复杂度是O(N)。所以总体时间复杂度是O(N)。

如果使用链表,插入新商品的方式如下:

e1b0a49863f03bd00f2b584fdc27c92b.png

如果要插入一个价格是3的商品,首先要知道这个商品应该插入的位置。链表无法使用二分查找,只能和原链表中的节点逐一比较大小来确定位置。这一步的时间复杂度是O(N)。

插入的过程倒是很容易,直接改变节点指针的目标,时间复杂度O(1)。因此总体的时间复杂度也是O(N)。

这对于拥有几十万商品的集合来说,这两种方法显然都太慢了。

因此引入一种不同的数据结构----跳跃表

首先,应该要了解跳跃表的性质;

由很多层结构组成;

每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;

最底层的链表包含了所有的元素;

如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);

链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

因此在前文中的商品列表排序中,可以取出若干关键节点,在新节点插入时先去比较关键节点,逐层下沉,最终在原链表中插入。这样,比较的次数下降了一半,节点数量少的情况下效果不明显,但在1w甚至10w个节点的链表下,性能的提升会非常明显。

5b10ece5e187b3c1aabdde5721b38fde.png

搜索:

839a36e78911cbf083c53b29633ae8fe.png

其基本原理就是从最高层的链表节点(关 键节点)开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。

1 find(x)2 {3 p =top;4 while (1) {5 while (p->next->key next;7 if (p->down ==NULL)8 return p->next;9 p = p->down;10 }11 }

插入节点 :

既然要插入,首先需要确定插入的层数,这里有不一样的方法。1. 看到一些博客写的是抛硬币,只要是正面就累加,直到遇见反面才停止,最后记录正面的次数并将其作为要添加新元素的层;2. 还有就是一些博客里面写的统计概率,先给定一个概率p,产生一个0到1之间的随机数,如果这个随机数小于p,则将高度加1,直到产生的随机数大于概率p才停止,根据给出的结论,当概率为1/2或者是1/4的时候,整体的性能会比较好(其实当p为1/2的时候,也就是抛硬币的方法)。

当确定好要插入的层数以后,则需要将元素都插入到从最底层到第k层

抛硬币法:

12476bf0107b9d81f55b0554ffe385ae.png

新节点和各层索引节点逐一比较,确定原链表的插入位置。O(logN)

把索引插入到原链表。O(1)

利用抛硬币的随机方式,决定新节点是否提升为上一级索引。结果为“正”则提升并继续抛硬币,结果为“负”则停止。O(logN)

总体上,跳跃表插入操作的时间复杂度是O(logN),而这种数据结构所占空间是2N,既空间复杂度是 O(N)。

删除节点

在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。

899c3e98064f255de6cf687ab7c1540e.png

自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点。O(logN)

删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外)。O(logN)

总体上,跳跃表删除操作的时间复杂度是O(logN)。

代码示例:

1 /*************************** SkipList.java *********************/

2

3 importjava.util.Random;4

5 public class SkipList>{6 private intmaxLevel;7 private SkipListNode[] root;8 private int[] powers;9 private Random rd = newRandom();10 SkipList() {11 this(4);12 }13 SkipList(inti) {14 maxLevel =i;15 root = newSkipListNode[maxLevel];16 powers = new int[maxLevel];17 for (int j = 0; j

26 for (int i = maxLevel - 2, j = 0; i >= 0; i--, j++)27 powers[i] = powers[i+1] - (2 <

28 }29 public intchooseLevel() {30 int i, r = Math.abs(rd.nextInt()) % powers[maxLevel-1] + 1;31 for (i = 1; i

34 return i-1; //return the highest level;

35 }36 //make sure (with isEmpty()) that search() is called for a nonempty list;

37 publicT search(T key) {38 intlvl;39 SkipListNode prev, curr; //find the highest nonnull

40 for (lvl = maxLevel-1; lvl >= 0 && root[lvl] == null; lvl--); //level;

41 prev = curr =root[lvl];42 while (true) {43 if (key.equals(curr.key)) //success if equal;

44 returncurr.key;45 else if (key.compareTo(curr.key) <0) { //if smaller, go down,

46 if (lvl == 0) //if possible

47 return null;48 else if (curr == root[lvl]) //by one level

49 curr = root[--lvl]; //starting from the

50 else curr = prev.next[--lvl]; //predecessor which

51 } //can be the root;

52 else { //if greater,

53 prev = curr; //go to the next

54 if (curr.next[lvl] != null) //non-null node

55 curr = curr.next[lvl]; //on the same level

56 else { //or to a list on a lower level;

57 for (lvl--; lvl >= 0 && curr.next[lvl] == null; lvl--);58 if (lvl >= 0)59 curr =curr.next[lvl];60 else return null;61 }62 }63 }64 }65 public voidinsert(T key) {66 SkipListNode[] curr = newSkipListNode[maxLevel];67 SkipListNode[] prev = newSkipListNode[maxLevel];68 SkipListNodenewNode;69 intlvl, i;70 curr[maxLevel-1] = root[maxLevel-1];71 prev[maxLevel-1] = null;72 for (lvl = maxLevel - 1; lvl >= 0; lvl--) {73 while (curr[lvl] != null && curr[lvl].key.compareTo(key) <0) {74 prev[lvl] = curr[lvl]; //go to the next

75 curr[lvl] = curr[lvl].next[lvl]; //if smaller;

76 }77 if (curr[lvl] != null && key.equals(curr[lvl].key)) //don't

78 return; //include duplicates;

79 if (lvl > 0) //go one level down

80 if (prev[lvl] == null) { //if not the lowest

81 curr[lvl-1] = root[lvl-1]; //level, using a link

82 prev[lvl-1] = null; //either from the root

83 }84 else { //or from the predecessor;

85 curr[lvl-1] = prev[lvl].next[lvl-1];86 prev[lvl-1] =prev[lvl];87 }88 }89 lvl = chooseLevel(); //generate randomly level

90 newNode = new SkipListNode(key,lvl+1); //for newNode;

91 for (i = 0; i <= lvl; i++) { //initialize next fields of

92 newNode.next[i] = curr[i]; //newNode and reset to newNode

93 if (prev[i] == null) //either fields of the root

94 root[i] = newNode; //or next fields of newNode's

95 else prev[i].next[i] = newNode; //predecessors;

96 }97 }98 }

有的时候跳跃表常用来代替红黑树,因为跳跃表在维持结构平衡时成本较低,完全靠随机,而红黑树这一类查找树每一次添加删除后都需要rebalance。

---恢复内容结束---


推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 众筹商城与传统商城的区别及php众筹网站的程序源码
    本文介绍了众筹商城与传统商城的区别,包括所售产品和玩法不同以及运营方式不同。同时还提到了php众筹网站的程序源码和方维众筹的安装和环境问题。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
author-avatar
HAOCWH
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有