数据结构与算法之美
王争
前 Google 工程师
283751 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 81 讲
基础篇 (38讲)
数据结构与算法之美
15
15
1.0x
00:00/00:00
登录|注册

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

小写字母、数字、大写字母的排序
小写字母排在大写字母前面,不要求内部有序
依次顺序遍历每个桶中的元素,得到有序用户数据
将用户根据年龄划分到120个桶中
对数据有要求,需要可以分割出独立的位来比较,位之间有递进关系
时间复杂度为O(k*n),k为位数
根据每一位来排序,借助稳定排序算法
适用于非负整数的排序
时间复杂度为O(n)
每个桶内的数据值都相同,省去了桶内排序的时间
当要排序的数据范围不大时,可以将数据划分成k个桶
特殊情况下的桶排序
适用于外部排序,数据量大,内存有限的情况
时间复杂度为O(n)
桶内排完序后,再把每个桶里的数据按顺序取出,组成有序序列
核心思想是将要排序的数据分到几个有序的桶里
对字符串进行特定排序
桶排序适用于外部排序,计数排序适用于非负整数排序,基数排序适用于可以划分位的排序
适用于特定的数据要求,能够高效地排序
桶排序、计数排序、基数排序都是线性排序算法,时间复杂度为O(n)
根据年龄给100万用户排序
基数排序
计数排序
桶排序
课后思考
总结
思考题解答
线性排序

该思维导图由 AI 生成,仅供参考

上两节中,我带你着重分析了几种常用排序算法的原理、时间复杂度、空间复杂度、稳定性等。今天,我会讲三种时间复杂度是 O(n) 的排序算法:桶排序、计数排序、基数排序。因为这些排序算法的时间复杂度是线性的,所以我们把这类排序算法叫作线性排序(Linear sort)。之所以能做到线性的时间复杂度,主要原因是,这三个算法是非基于比较的排序算法,都不涉及元素之间的比较操作。
这几种排序算法理解起来都不难,时间、空间复杂度分析起来也很简单,但是对要排序的数据要求很苛刻,所以我们今天的学习重点是掌握这些排序算法的适用场景
按照惯例,我先给你出一道思考题:如何根据年龄给 100 万用户排序? 你可能会说,我用上一节课讲的归并、快排就可以搞定啊!是的,它们也可以完成功能,但是时间复杂度最低也是 O(nlogn)。有没有更快的排序方法呢?让我们一起进入今天的内容!

桶排序(Bucket sort)

首先,我们来看桶排序。桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
桶排序的时间复杂度为什么是 O(n) 呢?我们一块儿来分析一下。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

三种时间复杂度为O(n)的排序算法:桶排序、计数排序、基数排序。桶排序适合处理大规模数据,计数排序适用于数据范围不大的场景,基数排序要求数据可以分割出独立的“位”来比较。这些算法对特定数据特征有严格要求,但若数据符合条件,应用这些算法能够实现高效排序,达到线性时间复杂度O(n)。桶排序和计数排序的思想相似,都是将数据划分成不同的桶来排序,而基数排序则要求数据可以划分成高低位,位之间有递进关系。此外,还有一些排序问题并不需要使用排序算法就能解决,如对字符串进行排序,要求将小写字母排在大写字母前面,或者对包含字母和数字的字符串进行排序。这些问题需要巧妙的处理方法而非传统排序算法。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《数据结构与算法之美》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(520)

  • 最新
  • 精选
  • wucj
    置顶
    用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。 对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理
    2018-10-19
    31
    1063
  • 胡二
    计数排序中,从数组A中取数,也是可以从头开始取的吧

    作者回复: 可以的 只不过就不是稳定排序算法了

    2018-10-19
    24
    138
  • Monday
    老师,个人感觉这节没有以往章节的细致了,拿桶排序来举例: 1、自问的三个问题只有了时间复杂度分析,少了是否为稳定排序算法和空间复杂度两个问题。 1.1)稳定性,若单个桶内用归并排序,则可保证桶排序的稳定性;若使用快排则无法保证稳定性。 1.2)空间复杂度,桶都是额外的存储空间,只有确定了单个桶的大小才能确定空间复杂度;n个元素假设分为m个桶,每个桶分配n/m个元素的大小?个人觉得单个桶的大小不好确定,但是范围应该在n/m~n之间 2、没有伪代码实现,自己在代码实现中遇到了一些问题 2.1)桶个数的设置依据什么原则? 2.2)桶大小的设置,让桶的能动扩容? 望回复,谢谢!

    作者回复: 1)这一节课的重点是应用场景 2)关于时间 空间 稳定性分析确实没有前面两节详细。不过通过前两节的学习 这三种排序算法的时间 空间 稳定性分析应该简单多了 3)你说的对 要用归并 4)桶的大小设置的原则 权衡空间 时间复杂度 在你能接受的执行时间和内存占用下完成就可以 并没有一个标准答案 5)是的 要么用链表 要么用动态扩容的数组

    2018-10-22
    3
    64
  • seniusen
    计数排序中可以从头向后取数据吗?个人感觉似乎是一样的过程。

    作者回复: 可以的 但就不是稳定排序算法了

    2018-10-19
    4
    39
  • Ant
    看了俩小时

    作者回复: 如果之前没基础 想掌握牢固 起码看一个礼拜吧😄

    2018-11-02
    9
    32
  • 林贻民
    老师你好,觉得计数排序可以完全被桶排序取代,由于计数排序对数据的要求是范围不大,不妨设为k,那完全可以分为k个桶,遍历一遍待排序列,将对应元素放入相关桶中,最后在按顺序遍历一次,即可得到顺序序列.在时间复杂度,空间复杂度上与计数排序一样,但是在代码编写上要比计数排序要简单的多.

    作者回复: 👍 你说的没错,在实践中,桶排序可能更实用些:)

    2019-03-04
    9
    18
  • 落叶飞逝的恋
    哈哈,老师,我把你的计数排序的代码关键代码处优化了下,并且加了中文注释,应该可以很好理解了。 public class CountingSort { /** * 计数排序 * * @param arr 要排序的数组大小 * @param n 数组元素个数 */ public static void sort(int[] arr, int n) { if (n <= 1) { return; } //默认数组最大的元素为数组第一个元素 int max = arr[0]; //遍历数组的所有的元素,找到最大的元素 for (int i = 1; i < n; i++) { //若后面的元素大于指定的数组元素,则把元素进行交换 if (arr[i] > max) { max = arr[i]; } } //申请一个计数数组,下标从0~max。 int[] c = new int[max + 1]; //遍历数组,将每个元素的个数放入到计数数组中,比如分数为0的元素,在c[0]就累加1,依次类推 for (int i = 0; i < n; i++) { c[arr[i]]++; } //开始重新整理c[]数组,将c[]数组顺序求和,比如分数0的个数1,分数为1的个数为3。那么重新整理后,分数<=0的为1,分数<=1人数诶1+3=4个,因为包含了<=0的个数,依次类推 //所以终止条件为i<=max for (int i = 1; i <= max; i++) { c[i] = c[i] + c[i - 1]; } //这时候开始进行排序,创建一个跟要排序的数组一样大小的数据空间 int[] temp = new int[n]; //开始循环需要排序的数据 for (int i = 0; i < n; i++) { //计算出需要往temp临时数组哪个索引位置存放arr[i]的值。 //根据原始数组的值找到计数数组的对应值的计数个数,得到c[arr[i]]的值,也就是temp数组从0开始,所以需要减一 int index = c[arr[i]] - 1; temp[index] = arr[i]; //每次循环,计数数组的元素值减一,因为数组放到了temp数组中 c[arr[i]]--; } //重新赋值 for (int i = 0; i < n; i++) { arr[i] = temp[i]; } } }

    作者回复: 👍

    2018-12-04
    4
    18
  • 叶明
    老师,你好,有个疑问: 在计数排序中,第一次得到数组int[] c = new int[]{2,0,2,3,0,1}之后, 能不能直接遍历数组c, int j=0; for(int i=0; i<c.length; i++){ int count = c[i]; for(int k=0;k<count;k++){ a[j++] = i; } } 这样不是也对所有的分数进行排序了吗?这个不知道可以不?第一次发言,希望能回复下

    作者回复: 如果你排序的不是单纯的数字 而是一个对象呢

    2018-12-17
    13
    16
  • coulson
    老师,你讲的一会数组C,一会数组R,一会数组A,已经被绕晕了,咋办?跟不上节奏了

    作者回复: 多看几遍 确实不好理解

    2018-10-30
    8
    13
  • 烈冬冰夏
    老师 您好。您说的手机号码排序伪代码向下面这样 sort(array,comparator)//对比第11位 sort(array,comparator)//对比第10位 sort(array,comparator)//对比第9位 sort(array,comparator)//对比第8位 sort(array,comparator)//对比第7位 sort(array,comparator)//对比第6位 sort(array,comparator)//对比第5位 sort(array,comparator)//对比第4位 sort(array,comparator)//对比第3位 sort(array,comparator)//对比第2位 sort(array,comparator)//对比第1位 我不太明白这和直接对比手机号码大小有什么区别 sort(array,comparator)//对比手机号码

    作者回复: 每一位排序用的是O(n)时间复杂度的桶排序或者计数排序

    2018-11-06
    2
    8
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部