排序算法(七)非比较排序:计数排序、基数排序、桶排序

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

非比较排序算法:计数排序基数排序桶排序。在一定条件下,它们的时间复杂度可以达到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

原文地址:https://www.cnblogs.com/xdyixia/p/9151938.html

时间: 2024-10-15 10:12:32

排序算法(七)非比较排序:计数排序、基数排序、桶排序的相关文章

九种经典排序算法详解(冒泡排序,插入排序,选择排序,快速排序,归并排序,堆排序,计数排序,桶排序,基数排序)

综述 最近复习了各种排序算法,记录了一下学习总结和心得,希望对大家能有所帮助.本文介绍了冒泡排序.插入排序.选择排序.快速排序.归并排序.堆排序.计数排序.桶排序.基数排序9种经典的排序算法.针对每种排序算法分析了算法的主要思路,每个算法都附上了伪代码和C++实现. 算法分类 原地排序(in-place):没有使用辅助数据结构来存储中间结果的排序**算法. 非原地排序(not-in-place / out-of-place):使用了辅助数据结构来存储中间结果的排序算法 稳定排序:数列值(key)

排序算法七:选择排序之堆排序

排序算法七:选择排序之堆排序 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 引言 在我的博文<"主宰世界"的10种算法短评>中给出的首个算法就是高效的排序算法.本文将对排序算法做一个全面的梳理,从最简单的"冒泡"到高效的堆排序等. 上博文讲述了选择排序中的简单排序算法,本文介绍的堆排序是树性选择排序,采用堆这个数据结构来辅助排序. 排序相关的的基本概念 排序:将一组杂乱无章的数据按一定的规律顺次排列起来. 数据

#排序算法#【1】概述、冒泡排序、选择排序

排序算法分类: 内部排序(在排序过程中不需要访问外存就可以完成排序) 外部排序 内部排序分类: 交换排序 冒泡排序 快速排序 选择排序 直接选择排序 堆排序 插入排序 直接插入排序 希尔排序 合并排序 外部排序: 常见的是多路归并算法,即将原文件分为多个能够一次装入内存一部分,分别把每一部分调入内存完成排序,然后对已经排序的子文件进行归并排序 冒泡排序法: 冒泡排序法是一种相邻数据交换的排序方法. 冒泡排序法的基本思想是:对待排序记录关键字从后往前(逆序)进行多遍扫描,当发现相邻两个关键字的次序

基数排序(桶排序) 不同方法

详细解释:算法导论/数据结构书 1.链式基数排序 //n个数,每个数有g个关键字//排序:从最后的关键字开始到最前的关键字//分配+收集//每个关键字分配+收集需要n+n时间,而共有g个关键字,时间复杂度为O(2ng),效率很高.//如果用数组,数据集中在一个区间,则区间的长度很长,另外的区间的内存浪费了.//用链表可以解决这个问题,且数据不需要先后顺序,所以无必要非要用数组 1 #include <stdio.h> 2 #include <stdlib.h> 3 //个数 4 #

常见的排序算法(四)( 归并排序,计数排序 , 基数排序)

 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为二路归并. (如果读者不太了解什么叫分治法,可以去看看<算法导论>第一二章.) 归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1:否则将第

排序算法七:基数排序(Radix sort)

上一篇提到了计数排序,它在输入序列元素的取值范围较小时,表现不俗.但是,现实生活中不总是满足这个条件,比如最大整形数据可以达到231-1,这样就存在2个问题: 1)因为m的值很大,不再满足m=O(n),计数排序的时间复杂也就不再是线性的: 2)当m很大时,为计数数组申请的内存空间会很大: 为解决这两个问题,本篇讨论基数排序(Radix sort),基数排列的思想是: 1)将先按照某个基数将输入序列的每个元素划分成若干部分,每个部分对排序结果的影响是有优先级的: 2)先按低优先级排序,再按高优先级

八大排序算法的python实现(八)简单选择排序

代码: #coding:utf-8 #author:徐卜灵 # L = [6, 3, 2, 32, 5, 4] def Select_sort(L): for i in range(0,len(L)): for j in range(i,len(L)): if L[i] > L[j]: #打擂台的形式 # temp = L[i] # L[i] = L[j] # L[j] = temp # L[i],L[j] = L[j],L[i] return L print Select_sort(L) 这个

必须知道的八大种排序算法【java实现】(二) 选择排序,插入排序,希尔算法【详解】

一.选择排序 1.基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换:然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止. 2.实例 3.算法实现 /** * 选择排序算法 * 在未排序序列中找到最小元素,存放到排序序列的起始位置 * 再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾. * 以此类推,直到所有元素均排序完毕. * @param numbers */ public static void selectSort(in

基数排序/桶排序-单链表实现

今天下午编程实现了基数排序(桶排序),只能说一千个人有一千个哈姆雷特,因此,一千个人可能有一千种基数排序的实现方式,无论是用数组,栈,队列,单链表(都是线性表哦, 好巧,哈哈).重要的是理解该排序算法的思路后,自己也就可以尝试着慢慢写出来了.时间关系,暂且只给出跟人代码(面试黄金月),以后有机会再补充实现思路.新手出道,代码可读性不要期望太高,多包涵,相信以后自己会进步的. typedef struct listnode // 定义节点 { int data; struct listnode *

Elias-Fano编码算法——倒排索引压缩用,本质上就是桶排序数据结构思路

Elias-Fano编码过程如下:把一组整数的最低l位连接在一起,同时把高位以严格单调增的排序划分为桶. Example: 2, 3, 5, 7, 11, 13, 24 Count in unary the size of upper bits "buckets" including empty ones:110=>计算最大的桶,此处是110,计算方法如下:Maximum bucket: [U / 2^l]Example: [24 / 2^2] = 6 = 110 连接最低位: