算法面试与实战-04排序算法- 时间复杂度为O(n)的排序算法(计数排序)

hi~上期我们学习了堆排序,是对二叉堆的再次应用,比较片面。这种的方法的事假复杂度是O(nlogn)有没有这更快的算法呢?

有的,我们本期就是学习这个算法-计数排序,什么叫做计数排序呢?

首先我们回顾一下以前学习算法,不管是冒泡排序,还是快速排序,堆排序等等都是基于元素的之间的比较来进行排序的。但是有一种特殊的排序算法叫做计数排序,他是利用数组下标来确定元素的正确位置。

计数排序是如何进行排序的呢?

例:有一个无序的数组:9,3,5,4,9,1,2,7,8,1,3,6,5,3,4,0,10,9 ,7,9

然后我们根据数组的取值范围建立一个[最小值,最大值]的数组区间。如上所示,建立一个长度为11的数组,数组下标从0-10,初始值设置为0。

那么如何给这个无序的数组进行排序呢?

操作步骤:我们遍历这个无序的随机数列,每一个整数按照其值对号入座,对应数组下标的元素进行加一操作。比如第一个整数是9,那么数组下标为9的元素加1:

第二个整数是3,那么数组下标为3的元素加1:

继续遍历数列并修改数组后...

最终,数组的状态如下:

数组的每一个下标位置的值,代表了数列中对应整数的出现的次数。

有了这个“计数结果”,排序就很简单了,直接遍历数组,输出元素的下标值,元素的值是几就输出几次。如下所示:

0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10

显然这个输出的数列已经是有序的了。

这就是计数排序的基本过程,它适合一定范围的整数培训,再取汁的范围不是很大的情况下,他的性能是大于那些(nlogn)的排序算法。

我们先来看看这个代码是如何实现的。

public class CountSort {
    public static int[] countSort(int[] array) {
        //得到数列的最大值
        int max = array[0];
        for (int i : array) {
            if (i > max) {
                max = i;
            }
        }
        //根据数列最大值确定统计数组的长度
        int[] countArray = new int[max + 1];
        //遍历数列,填充统计数组
        for (int i = 0; i < array.length; i++) {
            countArray[array[i]]++;
        }
        //遍历统计数组,输出结果
        int index = 0;
        int[] sortedArray = new int[array.length];
        for (int i = 0; i < countArray.length; i++) {
            for (int j = 0; j < countArray[i]; j++) {
                sortedArray[index++] = i;
            }
        }
        return sortedArray;
    }

    public static void main(String[] args) {
        int[] array = {9, 3, 5, 4, 9, 1, 2, 7, 8, 1, 3, 6, 5, 3, 4, 0, 10, 9, 7, 9};
        int[] ints = countSort(array);
        System.out.println(Arrays.toString(ints));
    }
}

从功能上,这段代码是实现了整数的排序。但是这段代码也存在一些问题,你有发现么?

是的,如果通过最大值来确定数组的长度的话就会出现问题,比如一组数据,900~1000,其实就100个数字,但是你通过长度来算的话就是1000笔数据,0~899其实是没有用的。那我们改怎么去优化呢?

解决方式:

我们不再以输入数列的最大值+1作为统计数组的长度,而是以数列的最大值和最小值的差+1作为数组的长度。同时,数列的最小值作为一个偏移量,用于统计数组的对号入座。 

比如这个数列:95,94,91,98,99,90,99,93,91,92

  1. 统计数组长度为 99-90=10,偏移量等于数列的最小值90。
  2. 对于第一个整数95,对应的统计数组下标是95-90=5,如图所示:

那这个代码改如何实现呢?

public class CountSortPlus {

    public static void countSort(int[] array){
        //根据差值确定数组长度
        int min=array[0]; int max=array[0];
        for (int i = 1; i <array.length ; i++) {
            if (max<array[i]){
                max=array[i];
            }
            if (min>array[i]){
                min=array[i];
            }
        }
        //构建新的数组
        int[] countArray = new int[max - min + 1];
        //遍历无序数组,并根据无序数组的值来更改countArray
        for (int i = 0; i <array.length ; i++) {
            //以最小值为基准
            int index = array[i] - min;
            countArray[index]=countArray[index]+1;
        }
        //遍历countArray 值是多少就遍历几次
        int flag=0;
        for (int i = 0; i <countArray.length ; i++) {
            for (int j = 0; j <countArray[i] ; j++) {
                array[flag++]=i+min;
            }
        }
        System.out.println(Arrays.toString(array));
    }

    public static void main(String[] args) {
        int[] array = {95, 93, 95, 99, 99, 91, 92, 97, 98, 91, 93, 96, 95, 93, 94, 90, 100, 99, 97, 99};
        countSort(array);
    }
}

 

进阶版:

如果我们想要原始数组中的相同元素按照本来的顺序的排列,那该怎么处理呢?以前面代码里的数组为例

int[] array = {95, 93, 95, 99, 99, 91, 92, 97, 98, 91, 93, 96, 95, 93, 94, 90, 100, 99, 97, 99};

比如这里有四个99,那我想要他们保持原来顺序。那我就要知道他们的初始顺序。然后在排序的时候将他们顺序保持不变即可。

那么具体代码是如何实现呢?

 public static int[] countSort(int[] array) {
        int min=array[0]; int max=array[0];
        for (int i = 1; i <array.length ; i++) {
           min=Math.min(min,array[i]);
           max=Math.max(max,array[i]);
        }
        //构建新的数组+1
        int[] countArray = new int[max - min + 1+1];
        //遍历无序数组,并根据无序数组的值来更改countArray 令countArray[0]永远为0
        for (int i = 0; i <array.length ; i++) {
            //以最小值为基准
            int index = array[i] - min+1;
            countArray[index]=countArray[index]+1;
        }
        //统计数组->变型,后面元素等于前面元素之和,元素的值表示这个数出现的次数,那么后面的元素就表示他在新的数组的位置
        int sum=0;
        for (int i = 1; i <countArray.length ; i++) {
            sum+=countArray[i];
            countArray[i]=sum;
        }
        //遍历
        int[] ints = new int[array.length];
        for (int i = 0; i <array.length ; i++) {
            ints[countArray[array[i]-min]]=array[i];
            countArray[array[i]-min]++;
        }
        return ints;
    }

综上所诉,我们可以看出计数排序的时间复杂度为O(N+M)所以可以归于)O(N)。但是为什么我们在开发中使用频率并不高呢?因为计数排序的有两个前提:

  • 一是需要排序的元素必须是整数
  • 二是排序元素的取值要在一定范围内,并且比较集中。

只有这两个条件都满足,才能最大程度发挥计数排序的优势。但是对于这种局限性,还有另外一种排序算法做了弥补,那就是下期我们学习的桶排序算法。下期见~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值