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

  • 相关阅读:
    Java实现 计蒜客 拯救行动
    Java实现 计蒜客 拯救行动
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 174 地下城游戏
    Java实现 LeetCode 173 二叉搜索树迭代器
    Java实现 LeetCode 173 二叉搜索树迭代器
    Visual Studio的SDK配置
    怎样使用CMenu类
    mfc menu用法一
  • 原文地址:https://www.cnblogs.com/xdyixia/p/9151938.html
Copyright © 2011-2022 走看看