您现在的位置是:主页 > news > 手机传奇网站/百度页面推广

手机传奇网站/百度页面推广

admin2025/5/6 20:06:52news

简介手机传奇网站,百度页面推广,19年做网站,html5/flash设计开发|交互设计|网站建设 青岛之前我们知道,二分查找依赖数组的随机访问,所以只能用数组来实现。如果数据存储在链表中,就真的没法用二分查找了吗?而实际上,我们只需要对链表稍加改造,就可以实现类似“二分”的查找算法,这种…

手机传奇网站,百度页面推广,19年做网站,html5/flash设计开发|交互设计|网站建设 青岛之前我们知道,二分查找依赖数组的随机访问,所以只能用数组来实现。如果数据存储在链表中,就真的没法用二分查找了吗?而实际上,我们只需要对链表稍加改造,就可以实现类似“二分”的查找算法,这种…

之前我们知道,二分查找依赖数组的随机访问,所以只能用数组来实现。如果数据存储在链表中,就真的没法用二分查找了吗?而实际上,我们只需要对链表稍加改造,就可以实现类似“二分”的查找算法,这种改造之后的数据结构叫作跳表(Skip List)。

1. 何为跳表?

对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低。

 

假如我们对链表每两个结点提取一个结点到上一级,然后建立一个索引指向原始结点,如下图所示。

 

这时候,我们要查找某一个数据的时候,就可以先在索引里面查找出一个大的范围,然后再下降到原始链表中精确查找。

比如,我们要查找 16,我们发现 16 位于 13 和 17 之间,这时候,我们就从 13 的地方下降到原始链表,然后再往后查询。原来我们查找 16,需要遍历 10 个结点,现在只需要遍历 7 个结点。

我们发现,加一层索引后,查找一个结点需要遍历的次数减少了,也就是查找效率提高了。

 

那么我们再多加一级索引呢?效果会不会有更大提升?

 

这一次,我们只需要遍历 6 个结点了。

数据量不大的时候这种方法可能效率提高得还不是很明显,下面看一个包含 64 个结点的例子,这次我们建立了五级索引。

查找 62 的时候原来需要遍历 62 次,现在只需要 11 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。

 

2. 跳表查询的分析?

如果链表中总共有 n 个结点,那么第一级索引就有 n/2 个结点,第二级索引就有 n/4 个结点,以此类推,那么第 k 级索引就有 n/2k 个结点。如果最高级索引有 2 个结点,那总的索引级数 k=log2n−1,如果我们算上原始链表的话,那也就是总共有 log2n级。

在第 k 级索引中,假设我们要查找的数据为 xx,当我们查找到 yy 结点时,发现 y<x<zy<x<z 时此时我们就要下降到 k−1级索引继续查找。在第 k−1级索引中,y 和 z 之间只有三个结点,因此,我们最多只需要查找 3 个结点。以此类推,每一级的索引最多都只需要遍历 3 个结点。

而总的级别数为 log2n,因此查找的时间复杂度就为 3∗log2n=logn3∗log2n=logn。跳表查找的时间复杂度和二分查找一样,但这其实是以空间来换时间的设计思路。

跳表的所有额外索引结点总数为 n/2+n/4+n/8+...+4+2=n−2/n+n/4+n/8+...+4+2=n−2,所以跳表的空间复杂度为 O(n)。

但如果我们每三个结点建立一个索引,这时候额外需要的结点总数为 n2,虽然空间复杂度依然为 O(n),但减少了一半的索引节点存储空间。

实际上,在实际开发中,原始链表中存储的可能是很大的对象,而索引结点只需要存储关键值和几个指针,其额外占用的空间可以被忽略掉。

 

3. 跳表高效的动态插入和删除?

 

在链表中,如果我们知道要插入数据的位置,那么插入的时间复杂度就为 O(1)。在跳表中,查找的时间复杂度为 O(logn),因此,动态插入数据的时间复杂度也就是 O(logn) 了。

 

从链表中删除结点的时候,如果结点在索引中也有出现,那么我们除了要删除原始链表中的结点,还要删除索引中的。

当我们不停地往跳表中插入数据的时候,如果我们不更新索引,就有可能出现某两个结点之间数据非常多的情况。极端情况下,跳表还会退化为单链表。

因此,我们需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表结点变多了,索引值就相应地增加一些。

 

当我们往跳表中插入数据的时候,我们可以选择同时也将这个数据插入到部分索引层中。而插入到哪些索引层中,则由一个随机函数生成一个随机数字来决定。如果这个数字为 K,那我们就将数据插入到第一级到第 K 级索引中。

 

 

4. 为什么 Redis 要用跳表来实现有序集合而不是红黑树?

Redis 中的有序集合支持的核心操作主要有以下几个:

  • 插入一个数据

  • 删除一个数据

  • 查找一个数据

  • 按照区间查找数据

  • 迭代输出有序序列

其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度和跳表是一样的。

但是,按照区间查找数据这个操作,红黑树的效率没有跳表高。跳表可以在 O(logn)O(logn) 时间复杂度定位区间的起点,然后在原始链表中顺序向后查询就可以了,这样非常高效。 

此外,相比于红黑树,跳表还具有代码更容易实现、可读性好、不容易出错、更加灵活等优点,因此 Redis 用跳表来实现有序集合。

 

5. 代码实现?

/*** @Description: TODO* @Author: luomingkui* @Date: Created in 上午10:59 2019/5/5* @Version: V1.0* 跳表的一种实现方法。* 跳表中存储的是正整数,并且存储的是不重复的。*/public class SkipList {private static final int MAX_LEVEL = 16;private int levelCount = 1;private Node head = new Node();  // 带头链表private Random r = new Random();public Node find(int value) {Node p = head;for (int i = levelCount - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}}if (p.forwards[0] != null && p.forwards[0].data == value) {return p.forwards[0];} else {return null;}}public void insert(int value) {int level = randomLevel();Node newNode = new Node();newNode.data = value;newNode.maxLevel = level;Node update[] = new Node[level];for (int i = 0; i < level; ++i) {update[i] = head;}// record every level largest value which smaller than insert value in update[]Node p = head;for (int i = level - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}update[i] = p;// use update save node in search path}// in search path node next node become new node forwords(next)for (int i = 0; i < level; ++i) {newNode.forwards[i] = update[i].forwards[i];update[i].forwards[i] = newNode;}// update node hightif (levelCount < level) levelCount = level;}public void delete(int value) {Node[] update = new Node[levelCount];Node p = head;for (int i = levelCount - 1; i >= 0; --i) {while (p.forwards[i] != null && p.forwards[i].data < value) {p = p.forwards[i];}update[i] = p;}if (p.forwards[0] != null && p.forwards[0].data == value) {for (int i = levelCount - 1; i >= 0; --i) {if (update[i].forwards[i] != null && update[i].forwards[i].data == value) {update[i].forwards[i] = update[i].forwards[i].forwards[i];}}}}// 随机 level 次,如果是奇数层数 +1,防止伪随机private int randomLevel() {int level = 1;for (int i = 1; i < MAX_LEVEL; ++i) {if (r.nextInt() % 2 == 1) {level++;}}return level;}public void printAll() {Node p = head;while (p.forwards[0] != null) {System.out.print(p.forwards[0] + " ");p = p.forwards[0];}System.out.println();}public class Node {private int data = -1;private Node forwards[] = new Node[MAX_LEVEL];private int maxLevel = 0;@Overridepublic String toString() {StringBuilder builder = new StringBuilder();builder.append("{ data: ");builder.append(data);builder.append("; levels: ");builder.append(maxLevel);builder.append(" }");return builder.toString();}}}

 

6.代码示例:https://github.com/luomingkui/dataalgo