数据结构与算法(二)——动态数据结构

动态数据结构是支持动态的更新操作,里面存储的数据是时刻在变化的。它支持快速地查询、快速插入、删除数据。

本文没有对各种动态数据结构的定义和实现进行分析,而是简要地讨论它们的优势、劣势和应用场景。因为对于工程应用来说,更重要的是根据这些数据结构的特点,合理地使用它们。

1、散列表

散列表使用哈希函数进行映射,因此查找、插入、删除操作的时间复杂度一般为O(1)。

限制:①要求设计合适的散列函数。要尽可能让散列后的值随机且均匀分布,这样会尽可能地减少散列冲突,即便冲突之后,分配到每个槽内的数据也比较均匀。并且,散列函数的设计也不能太复杂,太复杂就会太耗时间,也会影响散列表的性能。

②要求尽可能避免散列冲突。一般有开放寻址法和拉链法,开放寻址法在插入操作频繁的情况下,性能下降严重,拉链法更为普适。为了避免频繁插入后,遍历链表的时间复杂度太高,还可以将链表改为其他动态查找数据结构,比如红黑树,在jdk8中,HashMap底层就采用了这一策略。

缺点:①不能顺序遍历;②如果要扩容、缩容的话会出现性能损耗。

适用场景:适用于那些不需要顺序遍历,且数据插入删除并不频繁的情况。

2、跳表

跳表基于用空间换时间的原理,通过对有序链表构建多级索引来提高查询的效率,让链表也可以进行“二分查找”。支持快速的插入、删除、查找操作,时间复杂度都是 O(logn)。并且,可以实现顺序遍历、范围查找等操作。
在这里插入图片描述

缺点:空间复杂度O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,比如在插入操作时,跳表可以通过一个随机函数生成一个值,那么在插入数据的时候,就可以选择同时将这个数据插入到部分索引层中。这样就避免了频繁插入操作后,跳表退化成普通链表。

【极端情况】

在这里插入图片描述

【随机插入某一索引层后】

在这里插入图片描述

适用场景:适用于有顺序遍历、区间查找要求的场景。

3、红黑树

平衡二叉查找树又称AVL树,是在二叉查找树的基础上,对树的结构进行了更为严格的定义,即二叉查找树的任何节点的左右子树高度相差不超过 1。普通二叉查找树在经过某些插入、删除操作后可能会出现较为极端的情况,使得查询效率退化为O(logn)。为了避免这种情况,出现了平衡二叉树,它让整棵树变得匀称,树高也很低,维持较好的查询效率。

AVL 树对自身平衡性要求较高,每次插入、删除操作更复杂、耗时。因此,对于有频繁的插入、删除操作的数据集合,使用 AVL 树的代价较高。然后出现了红黑树这种"近似的平衡二叉树",它并没有达到AVL树那样的平衡度,而是做到了近似平衡,所以在维护平衡的成本上,红黑树优于AVL树。

红黑树的定义

  • 红黑树中的节点,一类被标记为黑色,一类被标记为红色;

  • 根节点是黑色的;

  • 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;

  • 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;

  • 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;

在这里插入图片描述

查询红黑树的时间复杂度可以维持在O(logn)(证明过程可以参考《算法》等教材),不会因为极端操作而导致性能退化,因此对于动态插入、删除、查找等操作,使用红黑树性能比较稳定

缺点:红黑树的实现非常复杂,如果自己写代码,难度很大,非要自己写代码的话,可以用跳表替代。

适用场景:在工程中,但凡用到动态插入、删除、查找数据的场景,都可以使用红黑树。

跳表和红黑树对比:

①对于插入、删除、查找以及迭代输出有序序列这几个操作,使用红黑树的时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。

②跳表代码更容易实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

这也是为什么Redis有序集合使用的是跳表而不是红黑树。

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/108775424
今日推荐