排序算法(八)——基数排序

基本思想

基数排序(Radix Sort)是在桶排序的基础上发展而来的,两种排序都是分配排序的高级实现。分配排序(Distributive Sort)的基本思想:排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。

先来看一下桶排序(Radix Sort)

桶排序也称为箱排序(Bin Sort),其基本思想是:设置若干个桶,依次扫描待排序的记录R[0],R[1],…,R[n-1],把关键字在某个范围内的记录全都装入到第k个桶里(分配),然后按序号依次将各非空的桶首尾连接起来(收集)。

例如,要将一副混洗的52张扑克牌按点数A<2<…<J<Q<K排序,需设置13个“桶”,排序时依次将每张牌按点数放入相应的桶里,然后依次将这些桶首尾相接,就得到了按点数递增序排列的一副牌。

桶排序中,桶的个数取决于关键字的取值范围。因此桶排序要求关键字的类型是有限类型,否则可能要无限个桶。

一般情况下每个桶中存放多少个关键字相同的记录是无法预料的,故桶的类型应设计成链表为宜。

为保证排序是稳定的,分配过程中装箱及收集过程中的连接必须按先进先出原则进行。

对于桶排序来说,分配过程的时间是O(n);收集过程的时间为O(m)(采用链表来存储输入的待排序记录)或O(m+n)。因此,桶排序的时间为O(m+n)。若桶个数m的数量级为O(n),则桶排序的时间是线性的,即O(n)。

前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。其次待排序的元素都要在一定的范围内等等。

基数排序是对桶排序的一种改进,这种改进是让“桶排序”适合于更大的元素值集合的情况,而不是提高性能。

我们还是用扑克牌的例子来说明。一张牌有两个关键字组成:花色(桃<心<梅<方)+面值(A<2<3<4<...<K)。假如一张牌的大小首先被花色决定,同花色的牌有数字决定的话。我们就有两种算法来解决这个问题。

即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花色情况下,大小关系才由面值的大小确定。这就是多关键码排序。

为得到排序结果,我们讨论两种排序方法。

方法1:先对花色排序,将其分为4个组,即梅花组、方块组、红心组、黑心组。再对每个组分别按面值进行排序,最后,将4 个组连接起来即可。

方法2:先按13 个面值给出13 个编号组(2 号,3 号,...,A 号),将牌按面值依次放入对应的编号组,分成13 堆。再按花色给出4 个编号组(梅花、方块、红心、黑心),将2号组中牌取出分别放入对应花色组,再将3 号组中牌取出分别放入对应花色组,……,这样,4 个花色组中均按面值有序,然后,将4 个花色组依次连接起来即可。

多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种方法:

最高位优先(Most Significant Digit first)法,简称MSD 法:

1)先按k1排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等。

2)再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后。

3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。

最低位优先(Least Significant Digit first)法,简称LSD 法:

1) 先从kd开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。

2) 最后将各个子序列连接起来,便可得到一个有序的序列,扑克牌按花色、面值排序中介绍的方法二即是LSD 法。

对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采"分配-收集”的方法进行排序,这一过程称作基数排序法其中每个数字或字符可能的取值个数称为基数。比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。

在“分配-收集”的过程中,需要保证排序的稳定性。

基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。比如下面的待排序列:

135、242、192、93、345、11、24、19

我们将每个数值的个位,十位,百位分成三个关键字,例如:

135 -> k1(个位)=5  ,k2(十位)=3 ,k3(百位)=1。

然后从最低位个位开始(从最次关键字开始),对所有数据的k1关键字进行桶分配(因为,每个数字都是 0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列。

(11)、(242、192)、(93)、(24)、(135、345)、(19)(从最次关键字开始排序,忽略百位与十位,按照个位上的数字分配)

再对上面的序列接着进行针对k2的桶分配,输出序列为:

(11、19)、(24)、(135)、(242、345)、(192、93)(参考最次关键字来排序第二次关键字,忽略百位与个位,按照十位上的数字分配)

最后针对k3的桶分配,输出序列为:

(011、019、024、093)、(135、192)、(242)、(345)(参考第二次关键字来排序最高关键字,忽略十位与个位,按照百位上的数字分配)

排序完毕。

java实现

public void radixSort(){

             int max = array[0];
             for(int i=0;i<array.length;i++){  //找到数组中的最大值
                    if(array[i]>max){
                           max = array[i];
                    }
             }

             int keysNum = 0;  //关键字的个数,我们使用个位、十位、百位...当做关键字,所以关键字的个数就是最大值的位数
             while(max>0){
                    max /= 10;
                    keysNum++;
             }

             List<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>();
             for(int i=0;i<10;i++){  //每位可能的数字为0~9,所以设置10个桶
                    buckets.add(new ArrayList<Integer>());  //桶由ArrayList<Integer>构成
             }

             for(int i=0;i<keysNum;i++){  //由最次关键字开始,依次按照关键字进行分配

                    for(int j=0;j<array.length;j++){  //扫描所有数组元素,将元素分配到对应的桶中
                           //取出该元素对应第i+1位上的数字,比如258,现在要取出十位上的数字,258%100=58,58/10=5
                           int key =array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
                           buckets.get(key).add(array[j]);  //将该元素放入关键字为key的桶中
                    }

                    //分配完之后,将桶中的元素依次复制回数组
                    int counter = 0;  //元素计数器
                    for(int j=0;j<10;j++){
                            ArrayList<Integer> bucket =buckets.get(j);  //关键字为j的桶
                            while(bucket.size()>0){
                                   array[counter++] = bucket.remove(0);  //将桶中的第一个元素复制到数组,并移除
                            }
                    }
                    System.out.print("第"+(i+1)+"轮排序:");
                    display();
             }

      }

打印结果如下:

算法分析

初看起来,基数排序的执行效率似乎好的让人无法相信,所有要做的只是把原始数据项从数组复制到链表,然后再复制回去。如果有10个数据项,则有20次复制,对每一位重复一次这个过程。假设对5位的数字排序,就需要20*5=100次复制。如果有100个数据项,那么就有200*5=1000次复制。复制的次数与数据项的个数成正比,即O(n)。这是我们看到的效率最高的排序算法。

不幸的是,数据项越多,就需要更长的关键字,如果数据项增加10倍,那么关键字必须增加一位(多一轮排序)。复制的次数和数据项的个数与关键字长度成正比,可以认为关键字长度是N的对数。因此在大多数情况下,基数排序的执行效率倒退为O(N*logN),和快速排序差不多。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-29 19:36:02

排序算法(八)——基数排序的相关文章

经典排序算法之基数排序(C语言版)

排序算法之基数排序的C语言实现. #include "math.h" #include "stdio.h" /* * 基数排序 2016-04-18 23:43:49 * 基数排序的思想:1.求出数组中最大的元素. *   2.求出最大元素是几位数.设为i位. *   3.对所有的数进行i轮排序.首先排个位,然后在十位,然后百位...... *   4.每一轮的排位都需要分桶,桶是有顺序的,然后在把桶里的数按顺序放入原来的数组中. *   5.直到i轮排序结束后,数

排序算法系列——基数排序

基数排序不同于其他的七种排序算法,它是基于一种分配法,而非比较.基数排序属于"分配式排序"(distribution sort),基数排序法又称"桶子法"(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用.它的灵感来自于队列(Queue),它最独特的地方在于利用了数字的有穷性(阿拉伯数字只有0到9的10个). 基本思想: 我们考虑对整数进行基数排序的过程,对于整数,

排序算法之基数排序

概述 首先从多排序码排序开始介绍基数排序.以扑克牌排序为例.每张扑克牌有两个"排序码":花色和面值.其有序关系为: 花色:?<?<?<? 面值:2<3<4<5<6<7<8<9<10<J<Q<K<A 如果把所有扑克牌排成以下次序: ?2,-,?A,?2,-,?A,?2,-,?A,?2,-,?A 这就是多排序码排序.排序后形成的有序序列叫做字典有序序列. 一般情况下,假定有一个n个元素的序列{V0,V

八大排序算法之基数排序

设计思想 它是根据关键字中各位的值,通过对排序的N个元素进行若干趟"分配"与"收集"来实现排序的.它不要比较关键字的大小. 假设:R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100} 任何一个阿拉伯数,它的各个位数上的基数都是以0~9来表示的.所以我们不妨把0~9视为10个桶. 我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中.例如:R[0] = 50,个位数上是0,将这个数存入编号为0的桶中. 分类后,我们在从各个桶中

常见的五类排序算法图解和实现(多关键字排序:基数排序以及各个排序算法的总结)

基数排序思想 完全不同于以前的排序算法,可以说,基数排序也叫做多关键字排序,基数排序是一种借助“多关键字排序”的思想来实现“单关键字排序”的内部排序算法. 两种方式: 1.最高位优先,先按照最高位排成若干子序列,再对子序列按照次高位排序 2.最低位优先:不必分子序列,每次排序全体元素都参与,不比较,而是通过分配+收集的方式. 多关键字排序 例:将下表所示的学生成绩单按数学成绩的等级由高到低排序,数学成绩相同的学生再按英语成绩的高低等级排序.        第一个关键字是数学成绩,第二个关键字是英

各种排序算法比较

各种常用排序算法 类别 排序方法 时间复杂度 空间复杂度 稳定性 复杂性 特点 最好 平均 最坏 辅助存储 简单 插入 排序 直接插入 Insert O(N) O(N2) O(N2) O(1) 稳定 简单 希尔排序 Shell O(N) O(N1.3) O(N2) O(1) 不稳定 复杂 选择 排序 直接选择 Select O(N) O(N2) O(N2) O(1) 不稳定 按理说平均复杂度和最坏复杂度相同是稳定排序.但是:序列5 8 5 2 9,第一遍选择第1个元素5会和2交换,那么原序列中2

各种排序算法时间复杂度

各种排序算法比较 各种常用排序算法 类别 排序方法 时间复杂度 空间复杂度 稳定性 复杂性 特点 最好 平均 最坏 辅助存储 简单 插入 排序 直接插入 O(N) O(N2) O(N2) O(1) 稳定 简单 希尔排序 O(N) O(N1.3) O(N2) O(1) 不稳定 复杂 选择 排序 直接选择 O(N) O(N2) O(N2) O(1) 不稳定 堆排序 O(N*log2N) O(N*log2N) O(N*log2N) O(1) 不稳定 复杂 交换 排序 冒泡排序 O(N) O(N2) O

【算法】基数排序

计数排序 学习基数排序之前首先学习计数排序. 计数排序假设每个元素都是在0到k之间的一个整数. 基数排序的基本思想,对于每个元素x,如果我们知道了小于x的元素的个数,就可以确定输出数组中元素x的位置,那么直接将元素x放到输出数组中.比如有3小于x的元素,那在输出数组中,x肯定位于第4个位置. 计数排序的算法用伪代码描述为: COUNTING-SORT(A,k) // 初始化数组C for i=0 to k C[i]=0 // 统计A[j]元素出现的次数,保存到C数组中 for j=0 to A.

几种排序算法及其代码实现(四)

四.归并排序 1.基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的.然后再把有序子序列合并为整体有序序列. 2.实例 3.java实现 1 packagecom.sort; 2 3 //稳定 4 public class 归并排序{ 5    public static void main(String[] args) { 6        int[] a={49,38,65,97,76,13,27,49,78

依赖数组特性的几种非比较排序算法

前言: 前面所讲的排序算法基本都是需要进行两个数依次比较,这种两个数依次比较的算法不依赖于数组重元素的特性并且有下界Ω(nlogn).换句话说就是使用比较排序算法最快的时间消耗没法小于这个界.那么是不是我们永远没法跨越这个梗呢?答案当然不是,当数组中的元素有一定的特点的时候,我们就可以利用这个特定,以实现排序算法的时间消耗与n呈线性的关系. 特性一:数组中所有元素正负性一致并且他们绝对值都小于某一个数. 当数组中所有元素都为正数或者都为负数的时候其实比较的算法是一致.这里我们假设所有元素都是非负