0
点赞
收藏
分享

微信扫一扫

读数据结构与算法之美(三)

绣文字 2022-02-19 阅读 89

11 - 排序(上):为什么插入排序比冒泡排序更受欢迎?

章节排序算法时间复杂度是否基于比较
11冒泡、插入、选择O(n²)
12归并、快排O(nlogn)
13桶、计数、基数O(n)

如何分析一个排序算法?

1.排序算法的执行效率

最好、最坏、平均情况时间复杂度

时间复杂度的系数、常数、低阶

比较次数和交换次数

2.排序算法的内存消耗(是否原地排序)

3.排序算法的稳定性(值相等的元素,排序后原有先后顺序不变)

冒泡排序(比较相邻元素,大的冒泡出来)

核心点:比较相邻的2个元素,看是否满足大小关系,不满足则对调。

表现上看,就是每次将最大/最小的数值冒出。(每次找最大)

1.冒泡排序是原地排序算法吗?

2.冒泡排序是稳定的排序算法吗?

3.冒泡排序的时间复杂度是多少?

插入排序(遍历index加入到排序区)

核心点:将序列分为排序区和非排序区。保证排序区始终有序。

1.冒泡排序是原地排序算法吗?

2.冒泡排序是稳定的排序算法吗?

3.冒泡排序的时间复杂度是多少?

选择排序(选最小的加到排序区)

核心点:与插入排序类似,也是分为排序区和非排序区。但是选择排序是从未排序区间找到最小的元素,将其放到已排序期间的末尾。(每次找最小)

1.冒泡排序是原地排序算法吗?

2.冒泡排序是稳定的排序算法吗?

3.冒泡排序的时间复杂度是多少?

为什么插入排序比冒泡排序更受欢迎呢?

插入排序的性能会优于冒泡排序。

12 - 排序(下):如何用快排思想在O(n)内查找第K大元素?

冒泡、插入、选择排序,适合小规模数据的排序。

归并排序、快速排序的时间复杂度为O(nlogn),更适合大规模的数据排序。

归并排序和快速排序,都用到了分治思想。

归并排序

核心点:将数组一分为二,分别排序,最后合并在一起。

分治是一种解决问题的处理思想,递归是一种编程技巧。

1.冒泡排序是原地排序算法吗?

2.冒泡排序是稳定的排序算法吗?

3.冒泡排序的时间复杂度是多少?

快速排序

核心点:选择任意一个元素作为分区点,分为三部分,递归划分,指到划分的小数组长度为1。

13 - 线性排序:如何根据年龄给100万用户数据排序?

三种时间复杂度是O(n)的排序算法:桶排序、计数排序、基数排序。重点是掌握这些排序算法的适用场景。

桶排序

例子:10GB订单数据,按订单金额排序

核心点:将有序的数据分到几个有序的桶里,每个桶里的数据再单独排序。

桶排序对数据的苛刻要求:

1.要排序的数据需要很容易划分成m个桶,桶之间有着天然的大小顺序。

2.数据在各个桶之间的分布比较均匀。

3.桶排序比较适合用在外部排序中。存储在外部磁盘中。

计数排序

计数排序其实是桶排序的一种特殊情况。

核心点:将数据划分成k个桶,每个桶的数据值相等。

例子:50w个考生成绩排序。

如何快速计算出,每个分数的考生在有序数组中对应的存储位置呢?

1.一个数组,存储每个分数对应的考生人数。然后对其进行顺序求和。(小于等于自己的人数)

计数排序,只能给非负整数排序,如果是其他类型,需要改为非负整数。例如,如果是小数的话乘以倍数转换成整数。如果是负数,加上一个正整数变为正数。

基数排序

例子:10w个手机号码排序。

先按照最后一位来排序,然后按倒数第二位重新排序,以此类推,最后按照最后一位重新排序。经过11次排序后,手机号码就有序了。

对于不等长的数据,需要补齐到等长。

对数据的要求:需要分割成独立的“位”来比较,位之间有递进的关系。每一位的数据范围不能太大,要可以用线性排序算法来排序,否则时间复杂度无法做到O(n)。

14 - 排序优化:如何实现一个通用的、高性能的排序函数?

如何选择合适的排序算法?

为了兼顾任意规模数据的排序,一般都会首选时间复杂度是O(nlogn)的排序算法来实现排序函数。

有归并排序、快速排序、堆排序。Java是基于堆排序实现,C是基于快排实现。

归并排序与之相比,最大的劣势就是空间复杂度高,非原地排序。

如何优化快速排序?

1.三数取中法

首、尾、中间,分别取一个数,取中间值作为分区点。数组太大时,可以五数取中、十数取中。

2.随机法

平均情况下,这样选的分区点是比较好的。

举例分析排序函数

在小规模数据面前,O(n²)时间复杂度的算法,并不一定比O(nlogn)的算法执行时间长。

15 - 二分查找(上):如何用最省内存的方式实现快速查找功能?

二分查找,针对的是有序集合,通过比较将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。

二分查找的递归与非递归实现

最简单的情况是不存在重复元素。

二分查找应用场景的局限性

1.依赖数据表结构,也就是数组

2.针对的是有序数据

3.数据量太小不适合

4.数据量太大不适合(数组需要申请连续的内存地址)

思考题:

1000w个整数数据,每个数据占8个字节,如何设计快速判断某个数是否在其中?内存不超过100MB

采用二分查找、散列表、二叉树都可以做,但是散列表、二叉树需要额外的内存存储数据结构,二分查找则只依赖数据,不需要额外内存。所以采用二分查找。

16 - 二分查找(下):如何快速定位IP对应的省份地址?

4种常见的二分查找变体问题

1.查找第一个值等于给定值的元素

2.查找最后一个值等于给定值的元素

3.查找第一个大于等于给定值的元素

4.查找最后一个小于等于给定值的元素

凡是用二分查找能解决的,绝大部分我们更倾向于用散列表或者二叉查找树。即便二分查找在内存使用上更节省,但是毕竟内存如此紧缺的情况并不多。

但是以上变体问题,使用二分查找则会更简单。

17 - 跳表:为什么Redis一定要用跳表来实现有序集合?

二分查找底层抵赖的是数组随机访问的特性,所以只能用数组来实现。如果数据存储在链表中,就真的没法用二分查找算法吗?

我们对链表稍加改造,就可以支持类似二分的查找算法。改造后的数据结构称为跳表。

跳表可以支持快速地插入、删除、查找操作,写起来也不复杂,甚至可以代替红黑树。

这种链表加多级索引的结构,就是跳表。

跳表的空间复杂度:O(n)

高效的动态插入和删除

跳表,不仅支持查找操作,还支持动态的插入、删除操作,而且时间复杂度都是O(logn)

跳表索引动态更新

作为一种动态数据结构,我们需要某种手段来维护索引和原始链表大小中间的平衡。也就是说,如果链表中结点多了,索引节点相应的增加,避免复杂度退化,以及查找、插入、删除操作性能下滑。

红黑树、AVL树这样平衡二叉树,是通过左右旋的方式保持左右子树的大小平衡。跳表是通过随机函数来维护平衡性。

当我们往跳表中插入数据的时候,我们可以选择同时将这个数据插入到部分索引层中。

如何选择加入哪些索引层?我们通过随机函数来决定。比如随机函数生成了值K,那我们就将这个结点添加到第一级到第K级这K级索引中。

跳表VS红黑树

1.跳表更容易代码实现,比起红黑树好懂、好写很多。简单意味着可读性好,不容易出错。

2.红黑树出现更早,很多编程语言的Map都是通过红黑树来实现的。跳表则没有现成的实现。

Redis的有序集合,一定要用跳表,因为在按区间查找时,跳表更有效率,会优于红黑树。

举报

相关推荐

0 条评论