zoukankan      html  css  js  c++  java
  • 排序算法(七)非比较排序:计数排序、基数排序、桶排序

    前面讲的是比较排序算法,主要有冒泡排序选择排序插入排序归并排序堆排序快速排序等。

    非比较排序算法:计数排序基数排序桶排序。在一定条件下,它们的时间复杂度可以达到O(n)。

    一,计数排序(Counting Sort)

    (1)算法简介

    计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

    (2)算法描述和实现

    1. 得到待排序数的范围(在这里增加了上界和下界);
    2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
    3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加),计算得到每个元素在排序后数组中的结束位置;
    4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

    实现

     1 public static void countSort(int[] array, int downBound, int upperBound) {  
     2     int[] countArray = new int[upperBound - downBound + 1];  
     3     if (upperBound < downBound)  
     4         return;  
     5     for (int i = 0; i < array.length; i++) {  
     6         countArray[array[i] - downBound]++;  
     7     }  
     8     int posSum = 0;  
     9     for (int i = 0; i < upperBound - downBound + 1; i++) {  
    10         posSum += countArray[i];  
    11         countArray[i] = posSum;  
    12     }  
    13     int[] result = new int[array.length];  
    14     for (int i = array.length - 1; i >= 0; i--) {  
    15         result[countArray[array[i] - downBound] - 1] = array[i];  
    16         countArray[array[i] - downBound]--;  
    17     }  
    18     for (int i = 0; i < array.length; i++) {  
    19         array[i] = result[i];  
    20     }  
    21 }  

    (3)算法分析

    当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存(如果数据比较分散,则在countArray中其实是有大量0的,占用很多空间)。

    最佳情况:T(n) = O(n+k)
    最差情况:T(n) = O(n+k)
    平均情况:T(n) = O(n+k)

    二,桶排序(Bucket Sort)

    桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。

    (1)算法简介

    桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排。

    (2)算法描述和实现

    1. 设置一个定量的数组当作空桶;
    2. 遍历输入数据,并且把数据一个一个放到对应的桶里去;
    3. 对每个不是空的桶进行排序;
    4. 从不是空的桶里把排好序的数据拼接起来。

    实现

     1 public static void bucketSort(int[] arr){  
     2       
     3     int max = Integer.MIN_VALUE;  
     4     int min = Integer.MAX_VALUE;  
     5     for(int i = 0; i < arr.length; i++){  
     6         max = Math.max(max, arr[i]);  
     7         min = Math.min(min, arr[i]);  
     8     }  
     9       
    10     //桶数  
    11     int bucketNum = (max - min) / arr.length + 1;  
    12     ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);  
    13     for(int i = 0; i < bucketNum; i++){  
    14         bucketArr.add(new ArrayList<Integer>());  
    15     }  
    16       
    17     //将每个元素放入桶  
    18     for(int i = 0; i < arr.length; i++){  
    19         int num = (arr[i] - min) / (arr.length);  
    20         bucketArr.get(num).add(arr[i]);  
    21     }  
    22       
    23     //对每个桶进行排序  
    24     for(int i = 0; i < bucketArr.size(); i++){  
    25         Collections.sort(bucketArr.get(i));  
    26     }  
    27

     下图给出了对{ 29, 25, 3, 49, 9, 37, 21, 43 }进行桶排序的简单演示过程

    (3)算法分析

    桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。

    最佳情况:T(n) = O(n+k)
    最差情况:T(n) = O(n+k)
    平均情况:T(n) = O(n2)

    三,基数排序(Radix Sort)

    (1)算法简介

    基数排序是按照低位先排序,然后收集(就是按低位排序);再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

    (2)算法描述和实现

    1. 取得数组中的最大数,并取得位数;
    2. arr为原始数组,从最低位开始取每个位组成radix数组;
    3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);
     1 public static void radixSort(int[] array, int maxDigit) {  
     2     int len = array.length;  
     3     int digitCount = 1;  
     4     int digitDev = 1;  
     5     int[] tmp = new int[len];  
     6     int[] count = new int[10];  
     7     while (digitCount <= maxDigit) {  
     8         Arrays.fill(count, 0);  
     9         Arrays.fill(count, 0);  
    10         for (int i = 0; i < len; i++) {  
    11             count[(array[i] / digitDev) % 10]++;  
    12         }  
    13         int sum = 0;  
    14         for (int i = 1; i < 10; i++) {  
    15             count[i] = count[i] + count[i - 1];  
    16         }  
    17         for (int i = len - 1; i >= 0; i--) {  
    18             tmp[count[(array[i] / digitDev) % 10] - 1] = array[i];  
    19             count[(array[i] / digitDev) % 10]--;  
    20         }  
    21         for (int i = 0; i < len; i++) {  
    22             array[i] = tmp[i];  
    23         }  
    24         digitDev *= 10;  
    25         digitCount++;  
    26     }  
    27 }  

    下图给出了对{ 329, 457, 657, 839, 436, 720, 355 }进行基数排序的简单演示过程

    (3)算法分析

    最佳情况:T(n) = O(n * k)
    最差情况:T(n) = O(n * k)
    平均情况:T(n) = O(n * k)

    基数排序 vs 计数排序 vs 桶排序

    这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

    基数排序:根据键值的每位数字来分配桶
    计数排序:每个桶只存储单一键值
    桶排序:每个桶存储一定范围的数值

    参考:

    http://www.cnblogs.com/eniac12/p/5332117.html

    https://www.cnblogs.com/jztan/p/5878630.html

    http://blog.csdn.net/wangqyoho/article/details/52584640

  • 相关阅读:
    String类中的常用方法(Java)
    Struts2的Lambda表达式的使用
    Struts2的环境搭建
    Servlet 3.0
    关于URLWriter的用法
    PrintStream与PrintWriter
    java中的System.nanoTime与System.currentTime
    java多线程之CyclicBarrier类
    多线程java IO之管道流
    消费者与生产者
  • 原文地址:https://www.cnblogs.com/xdyixia/p/9151938.html
Copyright © 2011-2022 走看看