基数排序——不会排序的程序员不是好快递员

Posted 嗨编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基数排序——不会排序的程序员不是好快递员相关的知识,希望对你有一定的参考价值。

版权声明:凡本号声明为原创的文章,禁止他人用于商业用途,欢迎转载,但请保留作者信息并且保证内容完整。


双十一、双十二一到,整个中国的物流都承受着巨大的压力,如何更快地把货物送到买家手里,很大程度上影响着买家的用户体验,同时也对快递公司的营收有着直接的影响。于是,机器人分拣,建立合适的中转仓......各种手段,十八般武艺都派上了用场,支撑这一切的核心当然是算法,这些措施能够让货物以“最优的方式”从一个城市到达另一个城市,不过最终把货物送到买家的,目前来说还得靠快递员。


一般来说,一个快递员负责一个片区的快递收发,那么,这个快递员负责的货物相对集中,尤其是学校的就更轻松了,Ta只要到学校的一个固定的地点摆摊,然后群发短信,等候买家上门领取就行了,这时候Ta只需要考虑如何快速地找到相应的货物。


如果货物不多,那么大概瞎找、目测这样没有章法的乱来都是可以的,可是货物一多,显然不行,尤其是双十一、双十二,货物更是多到没边。于是,快递员们努力研究,终于找到了好办法,请看:


基数排序——不会排序的程序员不是好快递员



快递员们使用的这种分类方法,在计算机科学中称之为 基数排序(Radix Sort,没错,排序可以起到分类的作用),这种排序算法在生活中也很常见,我们打扑克的时候,整理手牌所使用的方法,也是基数排序。


基数排序的核心思想就是分配+收集。我们来看一个例子,现在有23,12,25,88,66,36,49,45,13,95这10个数字需要排序。


我们首先依据个位上的数值把这些数字依次放入对应的“桶”里。

基数排序——不会排序的程序员不是好快递员


接下来,进行一轮收集,得到12,23,13,25,45,95,66,36,88,49,显然,此时还是无序的。


然后我们再依据十位上的数值把收集好的数字依次放入对应的“桶”里。


基数排序——不会排序的程序员不是好快递员


再次收集,得到12,13,23,25,36,45,49,66,88,95,这已经是一个有序的数字序列!


做一个简单的小结:


1.待排序对象的数据有K位(序列中的最大值有K位)的时候,需要进行K轮操作;

2.要准备10个“桶”(因为十进制数字有0~9共10种可能);

3.按照从低位到高位(自右向左)的顺序操作;

4.每一轮操作包含两个步骤:

i.按照该数位上的数值将数字分配到对应的“桶”里,分配的顺序依照当前数字序列的顺序

ii.从0号“桶”开始按顺序收集数字,先入桶的数字先被收集(为了方便,操作统一),形成新的数字序列。


按照上述算法,我们再针对203,12,325,288,666,36,49,45,137,95进行排序。


第一轮分配:


基数排序——不会排序的程序员不是好快递员


第一轮收集:

12,203,325,45,95,666,36,137,288,49。


第二轮分配:


基数排序——不会排序的程序员不是好快递员


第二轮收集:

203,12,325,36,137,45,49,666,288,95。


第三轮分配:


基数排序——不会排序的程序员不是好快递员


第三轮收集:

12,36,45,49,95,137,203,288,325,666。


排序完成。从这个例子可以看出,在某轮分配中,如果某些数字位数不足,那么用0补齐。


以下为算法实现代码,可跳过不读,懂得基本原理,会手工操作,就达到了基本的目的。


考虑算法的实现,逐条考虑:

1.K轮操作,循环实现即可;

2.10个“桶”,诸如数组此类容器均可实现;

3.按某位数值进行分配,需要用到数位分离算法;

4.在收集数字时,先入桶的数字先被收集,典型的队列结构。


代码截图

基数排序——不会排序的程序员不是好快递员


运行结果:

基数排序——不会排序的程序员不是好快递员

基数排序——不会排序的程序员不是好快递员


上述基数排序算法是自右向左的,学术上称之为“最低位优先(Least significant digital,LSD)”,那么我们自然会思考,有没有自左向右的“最高为优先(Most significant digital,MSD)”呢?


答案是肯定的,但是做法却不完全一致。我们再次对23,12,25,88,66,36,49,45,13,95这10个数进行排序,我们先沿袭LSD的做法。


第一轮分配:


基数排序——不会排序的程序员不是好快递员


第一轮收集:

12,13,23,25,36,49,45,66,88,95


第二轮分配:


基数排序——不会排序的程序员不是好快递员


第二轮收集:

12,13,23,25,45,95,36,66,88,49


显然,并没有如愿完成排序。那么问题究竟出在哪里呢?


在LSD过程中,每一轮分配都确定了该位的相对顺序,这种相对顺序在新的一轮分配中不会受到影响,比如个位的相对顺序确定后,在对十位进行分配的过程中,个位的相对顺序仍然得到保持(即如果十位相同,那么进入“桶”里的先后次序就是这两个数的个位大小顺序,因为收集时是按照0~9的顺序进行,而分配时的先后顺序依据上一次收集的顺序),因此在从低位到高位的分配过程中,每一位的相对顺序逐步确定,当最高位分配后,其顺序也就确定了下来。


例如上述203,12,325,288,666,36,49,45,137,95的例子,


第一轮收集后得到12,203,325,45,95,666,36,137,328,49


第二轮分配收集后得到203,12,325,36,137,4549,666,288,95。


我们关注45和49,这两个数个位的相对顺序没有改变。


接下来分析MSD,上述23,12,25,88,66,36,49,45,13,95的例子,


第一轮收集后得到12,13,23,25,36,49,45,66,88,95


第二轮收集后得到12,13,23,25,45,9536,66,88,49,


我们关注36和95,这两个数十位上的相对顺序已经变了,所以,沿袭LSD的算法,不能得到预期的排序结果。根本原因在于各位的权值不同,从低位到高位的分配过程中,当前分配的依据数位权值高于之前的所有数位,所以能保证当前数位往右至个位构成的数字相对顺序都是正确的;而从高位到低位的分配过程中,当前分配的依据数位权值低于之前的所有数位,所以可能会把之前按高位排好的顺序打乱!


不过,我们再仔细观察这两次分配的过程:


第一轮:


基数排序——不会排序的程序员不是好快递员


第二轮:


基数排序——不会排序的程序员不是好快递员


可以发现,其实第一轮分配好的某个桶内的数字,在新一轮分配中,它们的相对顺序是正确的!比如1号桶的12和13,2号桶的23和25,4号桶的49和45(分配好之后是45和49)。


所以,第一轮分配好之后,如果能把各个桶内的数字给排好了,再按照编号依次把桶内的数字输出,就应该是完成排序了。那么问题来了,桶内的数字又该怎么排呢?上一段话给了我们启发,我们可以继续使用第一轮的方法,针对次高位进行分配!至此,我们发现,这里存在着一个递归结构:


1.准备10个桶;

2.对待排序数字当前未分配的最高位进行分配;

3.按照编号扫描

i.如果桶内没有数字,直接跳过;

ii.如果桶内只有1个数字,直接输出;

iii.如果桶内超过1个数字,又分两种情况,如果已经分配到个位了,直接输出,否则针对桶内的数字序列进行收集,然后重复1~3。


排序过程:

第一轮不变:


基数排序——不会排序的程序员不是好快递员


1号桶内再分配:


基数排序——不会排序的程序员不是好快递员


至此可以直接输出12,13。


2号桶内再分配:


基数排序——不会排序的程序员不是好快递员


至此可以直接输出23,25。


3号桶直接输出36。


4号桶内再分配:


基数排序——不会排序的程序员不是好快递员


至此可以直接输出45,49。


6号桶直接输出66。


8号桶直接输出88。


9号桶直接输出95。


排序完成:12,13,23,25,36,45,49,66,88,95。


再看一个例子,给5个数21,10,21,43,32排序。


第一轮:


基数排序——不会排序的程序员不是好快递员


1号桶内直接输出10。


2号桶内再分配:


基数排序——不会排序的程序员不是好快递员


因为此时已经按个位分配,所以尽管当前1号桶有2个数,但也直接输出了21,21。


3号桶直接输出32。


4号桶直接输出43。


排序完成:10,21,21,32,43。


以下是代码实现。


最后我们再回顾快递员的操作,按照片所示,Ta只进行了一轮分配和收集,并没有完成最终的排序,尽管如此,查找效率已经大大提升了!所以,如果没学好算法,连快递员都不好做了。


ios用户专用赞赏二维码:


以上是关于基数排序——不会排序的程序员不是好快递员的主要内容,如果未能解决你的问题,请参考以下文章

基数排序是不是用于后缀排序?

PHP基数排序算法 - 类型转换好吗?

算法计数排序桶排序和基数排序详解

20191209-八大排序之基数排序

算法基数排序

排序算法 (11.基数排序)