排序(三)计数排序和基数排序

上次把时间复杂度趋近于O(nlogn)的算法写完了,这次先接着把时间复杂度O(n)的两个算法写完。它们分别是计数排序和基数排序,由于O(nlogn)已经是基于比较的排序算法的下限,这两个算法都不需要比较。

一、计数排序

时间复杂度O(n),空间复杂度O(k),k是输入序列的值的范围(最大值-最小值),是稳定的。计数排序一般用于已知输入值的范围相对较小,比如给公司员工的身高体重信息排序。

算法导论里面讲的计数排序大致是这样的思路:
假设n个输入元素中的每一个都是介于0到k之间的整数,对每一个输入元素x,确定出小于x的元素个数,有了这一信息,就可以把x直接放在它在最终输出数组的位置上,例如,如果有17个元素小于x,则x就是属于第18个输出位置。当几个元素相同是,方案要略作修改。

我感觉这种方法不太好理解,而且代码写起来也麻烦。所以我下面的代码是用桶排序的思想实现的。

这种实现有点像散列里的键值转换思想,把输入的值映射成桶的序号(桶数组的下标),然后再遍历一遍桶数组把元素倒出。过程大致是根据输入序列的值的范围k,创建k个桶(用大小为k的数组表示,下标表示桶的序号,值表示该桶里元素个数)。遍历一遍输入数组,放入相应位置的桶里,再把桶按顺序倒出来即可。

举个例子,假设输入数组A为{3,5,1,2,4,3},值的范围是1~5,所以创建5个桶,序号1,2,3,4,5。装桶时遍历一遍输入数组,A[0]=3,把它放到3号桶;A[1]=5,放到5号桶;1放到1号桶……最后3放到3号桶。现在三号桶的值为2,其他桶的值为1,再遍历一遍桶数组,按顺序把桶倒出,元素被倒出的顺序就是排序的顺序了。

import java.util.*;
 
public class CountingSort {
    public int[] countingSort(int[] A, int n) {
        //找数组中的最大值和最小值,确定桶的个数
        int max=A[0];
        int min=A[0];
        for(int i=0;i<n;i++){
            if(A[i]>max)
                max=A[i];
            if(A[i]<min)
                min=A[i];
        }
        //定义桶数组B并初始化
        int[] B= new int[max-min+1];
        for(int i=0;i<max-min+1;i++)
            B[i]=0;
        //把数组A的元素装到对应桶里
        for(int i=0;i<n;i++){
            B[A[i]-min]++;
        }
        //把所有桶倒出来
        for(int i=0,j=0;j<max-min+1;j++){
            //倒桶j
            for(int k=B[j];k>0;k--){
                A[i++]=j+min;
            }
        }
        return A;
    }
}

二、基数排序

1.多关键字排序思想

基数排序时一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。按照关键字先后顺序,基数排序可以分为高位优先法和低位优先法。

比如说,对扑克牌排序,规定花色(假设黑桃>梅花>红桃>方片)优先级高于面值,花色是主关键字,面值是次关键字。我们可以先按照花色将扑克牌分为有序的四堆,再对每一堆分别按照面值排序。这就是所谓的高位优先法。是不是感觉和归并有点像?这是因为这里也是利用了分治法思想进行的排序。
低位优先法则与上面相反。先按面值把扑克牌分成13堆(叫分配操作),再按顺序把这13堆从小到大叠起来(收集),然后再对花色执行分配收集操作,然后扑克就成有序的了。

再看值是数字的例子。假设输入为{234,112,73,252,31},我们可以把个位,十位,百位上的数字看作四个关键字,优先级依次升高。按上面的思想,先按百位分成三堆{73,31;112;234,252}(0;1;2),在每一堆内再分别按十位分,{31,73;112;234,252},再按个位分。这就是高位优先,低位优先和上面也是一样的道理。

一般来说低位优先法要比高位简单。因为高位优先是分治法思想,对子集按相同方法排序,是一个递归的过程。而低位优先法只要通过x次分配收集操作就可以完成排序,x是取决于关键字的多少。

2.桶排序思想实现基数排序

书上讲的基数排序算法就是利用桶的思想实现低位优先法,按照输入数值的各个位作为关键字进行排序。时间复杂度O(xn)=O(n),因为每次分配收集操作都是线性时间,关键词个数x也是常量。空间复杂度也大大减小,为O(1),桶的个数也是常量。是个稳定的排序算法。但是由于基数排序非常不灵活,输入数据类型稍有变化,可能就要重写,重新规定关键字等问题。所以基数排序并没有其他算法使用广泛。

下面讲算法的实现。也是利用桶排序思想。假设输入数据都是十进制的,都是四位数以内的数。我们准备十个桶,序号0,1,2,3,4,5,6,7,8,9。我们首先根据个位上的数值选择进入哪一个桶,然后按顺序倒出,进行第一次排序。然后再根据十位上的数进入桶,再倒出。然后百位,千位,进行四次分配收集。

计数排序算法里的桶只需要存储元素的个数,所以一个int值即可。但现在桶里面要存储元素的数值,所以每个桶就要用别的方式表示了。我在下面的代码中写的是
int [][] B = new int [10][n];
就是创建了十个桶,每个桶是大小为n的数组,因为每个桶里最多可能要放n个元素。但是这样写空间开销很大,大量空间都被浪费了。更好的做法应该是用链表来存储桶里的元素(但是我是写完才意识到的,就懒得改了。。。)。而且桶应该是用一个队列表示的。其实如果是在线做题的话,完全可以用一个Queue类表示,很简单。
我之前在做在线题的时候有一次需要用到堆,然后我自己就写了一个堆,花时间不说,还出错了。。。但其实完全可以用优先队列类辅助。面试的话面试官可能想考察你堆的掌握,但在线题只要OC就行了嘛。毕竟我们用的是java。反思自己还是对java不够熟悉,做算法题还是停留在c/c++的思维里,并没有利用java的特性。下面上代码,因为写的时候思路不清晰,而且没用链表队列表示桶,所以可能有点乱,大家凑乎看。

package fuckingtest;
import java.util.*;
public class RadixSort {
    public int[] radixSort(int[] A, int n) {
        //定义桶数组B并初始化,每个桶的第一个元素表示桶内元素个数
        int[][] B= new int[10][n+1];
        for(int i=0;i<10;i++)
            for(int j=0;j<n+1;j++)
               B[i][j]=0;        
        for(int x=1;x<=4;x++){
            //把数组A的第x位元素装到对应桶里
            for(int i=0;i<n;i++){
                B[getNum(A[i],x)][0]++;
                B[getNum(A[i],x)][B[getNum(A[i],x)][0]]=A[i];
            }
            //把所有桶倒出来
            for(int i=0,j=0;j<10;j++){
                //倒桶j
                for(int k=1;B[j][0]>0&&k<=B[j][0];k++){
                    A[i++]=B[j][k];
                }
                B[j][0]=0;
            }
        }
        return A;       
    }
    int getNum(int x,int d){
        int[] a={1,1,10,100,1000};
        System.out.println((x/a[d])%10);
        return (x/a[d])%10;
    }    
    public static void main(String[] args) {
        int[] r=new int[6];
        int[] a=new int[]{109,551,32,4,294,6};
        RadixSort h = new RadixSort();
        r = h.radixSort(a,6);
        for(int t:r){
            System.out.println(t);
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,245评论 0 2
  • 看看人家写的简单的说下就是1、用这种 而不用 2、写样式的时候可以用字母排序的方式写,如 这两段里找到margin...
    一沭丶阅读 186评论 0 1
  • 这么多年写下来的说说或许已很久很久不曾再去翻看,但它静静的躺在空间摆在那里就像一个里程碑。我也知道我曾倾心于...
    那些年聆听的阅读 148评论 0 0
  • 文/伊人若雨 今天在微信上和一个未曾谋面的北方朋友聊了一个很严肃的话题,这个话题是关于人的生活态度。他的观点...
    伊人若雨阅读 170评论 0 2