基数排序——不会排序的程序员不是好快递员
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,45,49,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,95,36,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用户专用赞赏二维码:
以上是关于基数排序——不会排序的程序员不是好快递员的主要内容,如果未能解决你的问题,请参考以下文章