8.5_2基数排序

基数排序:

每次比较的时候并不是比较关键字的大小

一组无序的数列要排列成递减的序列,每个关键字都是3位数个十百,即这3个位数的值都为0-9,即个/十/百位上有0-9共10种情况,所以建立10个辅助队列,分别对应0-9这10种情况,即Q0队列代表0,Q1队列代表1,。。。。。Q9队列代表个位9。靠近上边是队头方向,靠近下边是队尾方向

第一趟分配:先以个位进行分配。520的个位是0,则把520放在Q0队列上,211个位是1则放在Q1队列中,438个位是8则放在Q8队列中,888个位也是8则放在Q8队尾中,。。。。。其他同。168个位也是8则继续放在Q8队尾中,所有元素放在队列之后则分配结束。

 第一趟分配结束,收集。因为是要弄递减序列,所以要从个位较大的开始收集即从Q9队列往后收集,这样个位较大的数就排在前边,形成序列之后就是一个递减序列。Q9队列为空,Q8队列有3个元素从队头到队尾依次是438-888-168,把这三个元素从队列中拆下来变成438->888->168链表(靠近队头的排在前边,靠近队尾的排在后边),Q7队列有一个元素007,则把007拆下来放在刚才拆下来的链表的末尾即链表为438->888->168->007,Q6队列从头到尾(也就是从上到下)有666-996,依次放在007所在链表末尾即链表为438->888->168->007->666->996,Q5队列有一个元素985,则把985拆下来放在996所在链表的末尾即链表为438->888->168->007->666->996->985。。。。。其他同,从左往右即按各位递减的顺序把各个队列上的元素拆下来依次放到开始拆下来的链表末尾,直到把所有元素都放到链表上,则收集结束。。因为开始是按照个位递减分配的,所以收集时会得到一个按照个位递减的序列

 第2趟分配:基于第一趟收集的序列,以十位进行分配。从左到右第一个数438的十位是3,则把438放在Q3队列上,888十位是8则放在Q8队列中,168十位是6则放在Q6队列中,007十位是0则放在Q0队列中,666十位也是6则放在Q8队尾中(因为第2次分配是基于第一次分配基础上,所以当第2次分配的十位数相同时,个位数越大的越先入队,因为按照第一趟递减顺序,个位数越大的越排在前边,则在第2次分配时越先入队),。。。。。其他同。168个位也是8则继续放在Q8队尾中,所有元素放在队列之后则分配结束。

 第2趟分配结束,收集。同第一趟收集,从十位较大的开始收集即从Q9队列往后收集,这样较大的数就排在前边,形成序列之后就是一个递减序列。Q9队列有996,拆下来作为链表的头部,剩下从左往右依次从队列中拆下元素放到链表末尾,一个队列上有多个元素的,靠近队头的放在靠近队尾元素的前边。直到把所有元素都放到链表上,则收集结束。此时形成了十位上递减的序列,如果十位上值相同,则又按照个位上递减顺序排序(因为第一趟已经按个位上递减了,个位上递减的先入队,则在十位上递减时,十位相同个位上较大的也会排在前边)

第3趟分配:基于第2趟收集的序列,百位进行分配。从左到右第一个数996的百位是9,则996放在Q9队列上,888百位是8则放在Q8队列中,985百位也是9则放在Q9队列末尾(即百位上相同的,十位上越大的越先入队,保证了整体上更大的排在前边,更小的排在后边),。。。。。其他同。所有元素放在队列之后则分配结束。

 第3趟分配结束,收集。同第2趟收集,从百位较大的开始收集即从Q9队列往后收集,这样较大的数就排在前边,形成序列之后就是一个递减序列。Q9队列有996,拆下来作为链表的头部,剩下从左往右依次从队列中拆下元素放到链表末尾,一个队列上有多个元素的,靠近队头的放在靠近队尾元素的前边。直到把所有元素都放到链表上,则收集结束。此时形成了百位上递减的序列,如果百位上值相同,则又按照十位上递减顺序排序,如果十位上相同,则又按照个位上递减的顺序排序

基数排序定义:

长度为n的线性表:即长度为n的待排序无序序列,

元组:

每个节点的关键字分成d元组,即这个关键字有几位数构成就分成几元组,如下序列中的520、211、007啥的都是3位数则分成3元组,每个元组即每位上用k(d-1)表示,即三元组百位上的用k2,十位用k1,各位用k0表示,k(d-1)为最高位最高位上的数称为最高位关键字/最主未关键字,因为最高位对关键字的大小影响最大,如百位上的数肯定比各位上的影响更大,最低位上的数称为最低位/最次位关键字

基数r:

每一位即每个元组上可能的值的情况总数叫做基数,比如个/十/百位上的数可能值为0-9即共10个数,则基数r=10

基数排序得到递减序列过程:

1.设置基数r个队列,分别对应每个元组上的r种取值,因为要递减,则队列排列依次要先大后小,比如Q9到Q0对应9~0数字

2.因为是要得到递减序列,所以要依次对最低位/最高位关键字进行分配收集,要对d个关关键字位做分配收集即比如说这个数是三位数,那就得做个十百位3趟分配和收集

3.分配,顺序扫描位上的元素,放在对应的队列上,同值的放在队列末尾

4.收集,让各个分配好的队列上的元素出队排序链接

基数排序得到递增序列过程:

1.设置基数r个队列,分别对应每个元组上的r种取值,因为要递增,则队列排列依次要先小后大,比如Q0到Q9对应0~9数字

2.因为是要得到递增序列,所以要依次对最低位/最高位关键字进行分配收集,要对d个关关键字位做分配收集即比如说这个数是三位数,那就得做个十百位3趟分配和收集

3.分配,顺序扫描位上的元素,放在对应的队列上,同值的放在队列末尾

4.收集,让各个分配好的队列上的元素出队排序链接

 算法效率分析:

基数排序大都是基于链式存储结构实现的(如初始序列,每个关键字都是一个个节点),所以要定义个单链表LinkNode???

定义了10个队列,每个队列里链接了多个节点,所以每个队列的元素都是LinkNode节点,即定义了链队列数组LinkQueue Q[10],每个队列里定义了front队头指针指向最上边元素,rear队尾指针指向最下边元素,且队列里的每个节点LinkNode之间是有next指针连接起来的(即序列是通过链式结构实现的,则定义linknode链表节点,链表节点之间是通过next指针连接的,且10个队列为定义10个链队列数组即LinkQueue Q[10],每个队列里是链表节点元素LinkNode,且链队列元素里边还定义了front和rear指向头尾元素的指针)

空间复杂度:

O(r)。主要来源于定义的辅助队列长度,辅助队列长度取决于每个关键字位的值可能的情况即基数r大小,可能的情况越多,辅助队列长度就越大,每增大1个队列长度就会增加2个指针域(front和rear),每个队列需要的空间都是O(1),所以r长度的队列需要的空间为O(r)

时间复杂度:

O(d(n+r)),主要来源于分配和收集的时间。

 总共进行d趟分配和收集(d趟指的是一个关键字被分成几部分,被分成几部分就是d的值),每一趟的分配是要把序列上的所有关键字(指的是每个整体的关键字个数,比如这个序列有10个关键字,然后关键字有520倍分成了3部分,则要3趟分配和收集,分配的时候每趟都要对这10个关键字进行扫描)扫描,即假如这个序列有n个关键字,分配扫描一趟需要O(n)时间,一趟收集需要O(r)个时间(因为有r个队列,每个队列的元素都需要扫描一遍,把队列上的元素拆下来依次连到最终收集的链表中,前边开始从第一个队列里收集元素时就形成了一个单链表),每收集1个队列的元素需要O(1)的时间复杂度,则一共有r个队列需要O(r)个时间复杂度收集,则一趟分配+收集=O(n)+O(r),一共需要d趟,则要O(d(n+r))时间。

代码版从每个队列里收集元素:

如下图假设Q6之前队列的元素已经拆下来形成了一个链表LinkList,链表有一个头指针,设置此时链表的最后一个元素007的元素由p指针指向,接下来要把Q6队列里的元素拆下来放到007的后边,则让p->next=Q[6].front(即让链表的队尾指针指向Q[6]队列的队头元素,而Q6的队头元素由front指针指向,即让p->next=Q[6].front)即可,此时就完成了把一个队列上的元素全部拆下来的事情,整个过程不需要递归和循环,所以收集1个队列里的元素就只用O(1)的时间复杂度

稳定性:

基数排序是稳定的。 

 假设初始序列有2个相同的关键字12,前边的12没有下划线后边的12有下划线,在进行分配的时候是从左到右依次扫描则先扫描到没有下划线的12,则该12会被放到Q2队列的队头位置即front指向位置,则不管没带下划线的12后边有没有其他元素,带下划线12的肯定会被分到Q2队列的队尾即rear位置,在收集队列中的元素时,也是先从队列的队头->队尾收集,则没带下划线的12肯定会被放在带下划线12的链表前边的位置,即和初始序列相对位置相同,则稳定。基你太稳

 基数排序的应用:

如下假如学校有10000个学生即n=10000,要将学生信息按年龄递减排序,则可按学生的出生年月日拆分,拆分为年、月、日,即d=3,要进行3趟分配收集。在年龄中年所占的比重>月>日,按权重递增的顺序进行分配和收集,则第一趟分配和收集先对日进行,再依次对月和日进行分配收集。3个元组的基数r值还不同,年是1991~2005范围,则r=2005-1991+1=15种情况(即1991、1992、1993.。。。2005),月1-12则r=12-1+1=12种情况(即1,2,3.。。。12),日1-31则r=31-1+1=31种情况(即1、2、3、。。。31),r不同则要新增的辅助队列Q也不同,第一趟对日分配收集需要新建r=31个辅助队列,第二趟对月分配需要r=12个,第三趟需要r=15个,因为是要递减顺序,日期/月份/年份越小年龄越大,则收集的时候要先收集小的再收集大的(年月日同),即队列的排序要从小到大。则O(10000)=O(3*(10000+31))=O(30000)【收集时每趟r不同的,计算时间复杂度时选r最大的就行,即为最坏时间复杂度】,即不是每个基数排序的关键字的取值范围都是0~r-1即都是纯数字啥的,上边这个例子每个辅助队列啥的长度和代表的值都不同。。。。。。

基数排序适用条件:

1.序列中每个关键字可以拆分成d组,且d较小

2.拆分成的每个元组的值取值范围不大,即r较小

3.序列中的元素总个数n较大

反例:

给5个人的身份证号排序,每个人身份证号有18位,则n=5不满足适用条件第3条,且拆分的话要拆分成18组,不满足适用条件第1条即d较大,此时不适用基数排序,但是如果是给10亿人的身份证号排序,即使d较大,但是此时n也很大,还是推荐使用的

给中国人的中文名字排序,每个中文名字基本上是2-5位,即d较小,但是此时r较大,因为中国汉字太多了,此时不适用基数排序

 

知识回顾:

 

。。。。。。。。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值