各种排序笔记---基于非比较排序部分

在计算机科学中,排序是一门基础的算法技术,许多算法都要以此作为基础,不同的排序算法有着不同的时间开销和空间开销。排序算法有非常多种,如我们最常用的快速排序和堆排序等算法,这些算法需要对序列中的数据进行比较,因为被称为基于比较的排序

基于比较的排序算法是不能突破O(NlogN)的。简单证明如下:

N个数有N!个可能的排列情况,也就是说基于比较的排序算法的判定树有N!个叶子结点,比较次数至少为log(N!)=O(NlogN)(斯特林公式)。

非基于比较的排序,如计数排序,桶排序,和在此基础上的基数排序,则可以突破O(NlogN)时间下限。但要注意的是,非基于比较的排序算法的使用都是有条件限制的,例如元素的大小限制,相反,基于比较的排序则没有这种限制(在一定范围内)。但并非因为有条件限制就会使非基于比较的排序算法变得无用,对于特定场合有着特殊的性质数据,非基于比较的排序算法则能够非常巧妙地解决。

基于非比较的排序算法有三种,计数排序,桶排序和基数排序。

-----------------------------我是分割线-------------------------------------------------------

1. 计数排序

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

 特征:

当输入的元素是n个0到k之间的整数时,它的运行时间是Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序算法中,能够更有效的排序数据范围很大的数组。

通俗地理解,例如有10个年龄不同的人,统计出有8个人的年龄比A小,那A的年龄就排在第9位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去1的原因。算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组 C 的第 i 项
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

java 实现:

 1 public class CountingSort {
 2     public static void main(String[] argv) {
 3         int[] A = CountingSort.countingSort(new int[]{16, 4, 10, 14, 7, 9, 3, 2, 8, 1});
 4         Utils.print(A);
 5     }
 6 
 7     public static int[] countingSort(int[] A) {
 8         int[] B = new int[A.length];
 9         // 假设A中的数据a'有,0<=a' && a' < k并且k=100
10         int k = 100;
11         countingSort(A, B, k);
12         return B;
13     }
14 
15     private static void countingSort(int[] A, int[] B, int k) {
16         int[] C = new int[k];
17         // 计数
18         for (int j = 0; j < A.length; j++) {
19             int a = A[j];
20             C[a] += 1;
21         }
22         Utils.print(C);
23         // 求计数和
24         for (int i = 1; i < k; i++) {
25             C[i] = C[i] + C[i - 1];
26         }
27         Utils.print(C);
28         // 整理
29         for (int j = A.length - 1; j >= 0; j--) {
30             int a = A[j];
31             B[C[a] - 1] = a;
32             C[a] -= 1;
33         }
34     }
35 }
36 
37 
38 //针对c数组的大小,优化过的计数排序
39 public class CountSort{
40     public static void main(String []args){
41         //排序的数组
42         int a[] = {100, 93, 97, 92, 96, 99, 92, 89, 93, 97, 90, 94, 92, 95};
43         int b[] = countSort(a);
44         for(int i : b){
45             System.out.print(i + "  ");
46         }
47         System.out.println();
48     }
49     public static int[] countSort(int []a){
50         int b[] = new int[a.length];
51         int max = a[0], min = a[0];
52         for(int i : a){
53             if(i > max){
54                 max = i;
55             }
56             if(i < min){
57                 min = i;
58             }
59         }
60         //这里k的大小是要排序的数组中,元素大小的极值差+1
61         int k = max - min + 1;
62         int c[] = new int[k];
63         for(int i = 0; i < a.length; ++i){
64             c[a[i]-min] += 1;//优化过的地方,减小了数组c的大小
65         }
66         for(int i = 1; i < c.length; ++i){
67             c[i] = c[i] + c[i-1];
68         }
69         for(int i = a.length-1; i >= 0; --i){
70             b[--c[a[i]-min]] = a[i];//按存取的方式取出c的元素
71         }
72         return b;
73     }
74 }
count sort

优化后的代码:

 1 public static void Sort(int[] A, int k)
 2         {
 3             Debug.Assert(k > 0);
 4             Debug.Assert(A != null);
 5 
 6             int[] C = new int[k + 1];
 7 
 8             for (int j = 0; j < A.Length; j++)
 9             {
10                 C[A[j]]++;
11             }
12 
13             int z = 0;
14 
15             for (int i = 0; i <= k; i++)
16             {
17                 while (C[i]-- > 0)
18                 {
19                     A[z++] = i;
20                 }
21             }
22         }
View Code

由于C数组下标 i 就是A 的值,所以我们不需要保留A中原来的数了,这个代码减少了一个数组B,而且要比原来的代码简化了很多。

-----------------------------我是分割线------------------------------------------------------

 

2. 桶排序

可能你会发现,计数排序似乎饶了点弯子,比如当我们刚刚统计出C,C[i]可以表示A中值为i的元素的个数,此时我们直接顺序地扫描C,就可以求出排序后的结果。的确是这样,不过这种方法不再是计数排序,而是桶排序(Bucket Sort),确切地说,是桶排序的一种特殊情况。

 

无序数组有个要求,就是成员隶属于固定(有限的)的区间,如范围为[0-9](考试分数为1-100等)

例如待排数字[6 2 4 1 5 9]

准备10个空桶,最大数个空桶

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 0 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

1,顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶,这个过程类似这样:空桶[ 待排数组[ 0 ] ] = 待排数组[ 0 ]

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

2,顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶

[6 2 4 1 5 9]           待排数组

[0 0 2 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

3,4,5,6省略,过程一样,全部入桶后变成下边这样

[6 2 4 1 5 9]           待排数组

[0 1 2 0 4 5 6 0 0 9]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

0表示空桶,跳过,顺序取出即可:1 2 4 5 6 9

image

 1 void bucketSort(int a[], int n, int max)
 2 {
 3     int i,j;
 4     int buckets[max];
 5 
 6     // 将buckets中的所有数据都初始化为0。
 7     memset(buckets, 0, max*sizeof(int));
 8 
 9     // 1. 计数
10     for(i = 0; i < n; i++) 
11         buckets[a[i]]++; 
12 
13     // 2. 排序
14     for (i = 0, j = 0; i < max; i++) 
15     {
16         while( (buckets[i]--) >0 )
17             a[j++] = i;
18     }
19 }
bucket sorting

这种特殊实现的方式时间复杂度为O(N+K),空间复杂度也为O(N+K),同样要求每个元素都要在K的范围内。更一般的,如果我们的K很大,无法直接开出O(K)的空间该如何呢?

首先定义桶,桶为一个数据容器,每个桶存储一个区间内的数。依然有一个待排序的整数序列A,元素的最小值不小于0,最大值不超过K。假设我们有M个桶,第i个桶Bucket[i]存储K * (i/M) 至 k * (i+1)/M之间的数,有如下桶排序的一般方法:

  1. 扫描序列A,根据每个元素的值所属的区间,放入指定的桶中(顺序放置)。
  2. 对每个桶中的元素进行排序,什么排序算法都可以,例如快速排序。
  3. 依次收集每个桶中的元素,顺序放置到输出序列中。

对该算法简单分析,如果数据是期望平均分布的,则每个桶中的元素平均个数为N/M。如果对每个桶中的元素排序使用的算法是快速排序,每次排序的时间复杂度为O(N/Mlog(N/M))。则总的时间复杂度为O(N)+O(M)O(N/Mlog(N/M)) = O(N+ Nlog(N/M)) =O(N + NlogN - NlogM)。当M接近于N是,桶排序的时间复杂度就可以近似认为是O(N)的。就是桶越多,时间效率就越高,而桶越多,空间却就越大,由此可见时间和空间是一个矛盾的两个方面。

桶中元素的顺序放入和顺序取出是有必要的,因为这样可以确定桶排序是一种稳定排序算法,配合基数排序是很好用的。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cmath>
 5 #include <cstring>
 6 using namespace std;
 7 struct linklist
 8 {
 9     linklist *next;
10     int value;
11     linklist(int v,linklist *n):value(v),next(n){}
12     ~linklist() {if (next) delete next;}
13 };
14 inline int cmp(const void *a,const void *b)
15 {
16     return *(int *)a-*(int *)b;
17 }
18 /*
19 为了方便,我把A中元素加入桶中时是倒序放入的,而收集取出时也是倒序放入序列的,所以不违背稳定排序。
20 */
21 void BucketSort(int *A,int *B,int N,int K)
22 {
23     linklist *Bucket[101],*p;//建立桶
24     int i,j,k,M;
25     M=K/100;
26     memset(Bucket,0,sizeof(Bucket));
27     for (i=1;i<=N;i++)
28     {
29         k=A[i]/M; //把A中的每个元素按照的范围值放入对应桶中
30         Bucket[k]=new linklist(A[i],Bucket[k]);
31     }
32     for (k=j=0;k<=100;k++)
33     {
34         i=j;
35         for (p=Bucket[k];p;p=p->next)
36             B[++j]=p->value; //把桶中每个元素取出,排序并加入B
37         delete Bucket[k];
38         qsort(B+i+1,j-i,sizeof(B[0]),cmp);
39     }
40 }
41 int main()
42 {
43     int *A,*B,N=100,K=10000,i;
44     A=new int[N+1];
45     B=new int[N+1];
46     for (i=1;i<=N;i++)
47         A[i]=rand()%K+1; //生成1..K的随机数
48     BucketSort(A,B,N,K);
49     for (i=1;i<=N;i++)
50         printf("%d ",B[i]);
51     return 0;
52 }
View Code

 例题:

(1)sort color

Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue.

Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Notice

You are not suppose to use the library's sort function for this problem. 

You should do it in-place (sort numbers in the original array).

Have you met this question in a real interview?

Yes

Example

Given [1, 0, 1, 2], sort it in-place to [0, 1, 1, 2].

次优方法 桶排序/计数排序 代码:

 1 public class Solution {
 2     public void sortColors(int[] nums) {
 3         int[] count = new int[3];
 4         for (int i = 0; i < nums.length; i++) {
 5             count[nums[i]]++;
 6         }
 7         int j = 0;
 8         for (int i = 0; i < nums.length;i++) {
 9             if (count[j] > 0) {
10                 nums[i] = j;
11                 count[j]--;
12             } else  {
13                 j++;
14                 i--;
15             }
16         }
17     }
18 }
View Code

由于这个题目只有3个值,也就是拿到数,可以判断是前中后哪个部分的。所以可以用2根指针遍历的方式去实现

最优方法 两根指针

 1 public class Solution {
 2     public void sortColors(int[] nums) {
 3         int red = 0, current = 0, blue = nums.length - 1;
 4         while (current <= blue) {
 5             if (nums[current] == 0) {
 6                 swap(red, current, nums);
 7                 red++;
 8                 current++;
 9             } else if (nums[current] == 2) {
10                 swap(current, blue, nums);
11                 blue--;
12             } else {
13                 current++;
14             }
15         }
16         
17         
18         
19     }
20     private static void swap(int i, int j, int[] nums) {
21         int temp = nums[i];
22         nums[i] = nums[j];
23         nums[j] = temp;
24     }
25 }
View Code

 

 

-----------------------------我是分割线----------------------------------------------------------------------

 

 

3 基数排序(Radix Sort)

上述的基数排序和桶排序都只是在研究一个关键字的排序,现在我们来讨论有多个关键字的排序问题。

假设我们有一些二元组(a,b),要对它们进行以a为首要关键字,b的次要关键字的排序。我们可以先把它们先按照首要关键字排序,分成首要关键字相同的若干堆。然后,在按照次要关键值分别对每一堆进行单独排序。最后再把这些堆串连到一起,使首要关键字较小的一堆排在上面。按这种方式的基数排序称为MSD(Most Significant Dight)排序。

第二种方式是从最低有效关键字开始排序,称为LSD(Least Significant Dight)排序。首先对所有的数据按照次要关键字排序,然后对所有的数据按照首要关键字排序。要注意的是,使用的排序算法必须是稳定的,否则就会取消前一次排序的结果。由于不需要分堆对每堆单独排序,LSD方法往往比MSD简单而开销小。下文介绍的方法全部是基于LSD的。

通常,基数排序要用到计数排序或者桶排序。使用计数排序时,需要的是Order数组。使用桶排序时,可以用链表的方法直接求出排序后的顺序。下面是一段用桶排序对二元组基数排序的程序:

基数排序是非比较排序算法,算法的时间复杂度是O(n). 相比于快速排序的O(nlgn),从表面上看具有不小的优势.但事实上可能有些出入,因为基数排序的n可能具有比较大的系数K.因此在具体的应用中,应首先对这个排序函数的效率进行评估.

基数排序的主要思路是,将所有待比较数值(注意,必须是正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次稳定排序(我们常用上一篇blog介绍的计数排序算法, 因为每个位可能的取值范围是固定的从0到9).这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.

比如这样一个数列排序: 342 58 576 356, 以下描述演示了具体的排序过程(红色字体表示正在排序的数位)

第一次排序(个位):

3 4 2

5 7 6

3 5 6

0 5 8

第二次排序(十位):

4 2

5 6

5 8

7 6

第三次排序(百位):

0 5 8

3 4 2

3 5 6

5 7 6

结果: 58 342 356 576

两个问题:

  • 为什么要从低位开始向高位排序?

        如果要从高位排序, 那么次高位的排序会影响高位已经排好的大小关系. 在数学中, 数位越高,数位值对数的大小的影响就越大.从低位开始排序,就是对这种影响的排序. 数位按照影响力从低到高的顺序排序, 数位影响力相同则比较数位值.

  • 为什么同一数位的排序子程序要使用稳定排序?

        稳定排序的意思是指, 待排序相同元素之间的相对前后关系,在各次排序中不会改变.比如实例中具有十位数字5的两个数字58和356, 在十位排序之前356在58之前,在十位排序之后, 356依然在58之前.

        稳定排序能保证,上一次的排序成果被保留,十位数的排序过程能保留个位数的排序成果,百位数的排序过程能保留十位数的排序成果.

-----------------------------我是分割线-----------------------------------------------------

 

转载于:https://www.cnblogs.com/jiangchen/p/5918507.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/417896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vbs特殊符号

常数 值 描述 vbCr Chr(13) 回车符。 vbCrLf Chr(13) & Chr(10) 回车符与换行符。 vbFormFeed Chr(12) 换页符&#xff1b;在 Microsoft Windows 中不适用。 vbLf Chr(10) 换行符。 vbNewLine Chr(13) & Chr(10) 或 Chr(10) 平台指定的新行字符&#xff1b;适用于…

在mysql表中如何变换列和行_在SQL中转换列和行的简单方法?

有几种方法可以转换这些数据。在你最初的帖子中&#xff0c;你说PIVOT对于这个场景来说似乎太复杂了&#xff0c;但是可以很容易地使用UNPIVOT和PIVOTSQL Server中的函数。但是&#xff0c;如果您无法访问这些函数&#xff0c;则可以使用UNION ALL到UNPIVOT然后是一个具有CASE向…

powerpoint文字教程

建立空白演示文稿 如果所有模板都不满足要求&#xff0c;或者想制作一个特殊的、具有与众不同外观的演示文稿&#xff0c;可从一个空白演示文稿开始&#xff0c;自建背景设计、配色方案和一些样式特性。选择“空演示文稿”选项&#xff0c;或者在PowerPoint 2000窗口中&#xf…

不同语言,系统通过共享内存方式实现信息交互

1. 两个程序映射同一个文件到自己的地址空间2. 进程A先运行, 每隔两秒读取映射区域, 看是否发生变化. 3. 进程B后运行, 它修改映射区域, 然后推出, 此时进程A能够观察到存储映射区的变化一个读&#xff0c;一个写。转载于:https://www.cnblogs.com/swbzmx/p/5992679.html

如何使用用window.open()

oNewWindow window . open ( sURL , sName , sFeatures , bReplace ) 参数&#xff1a;sUrl : 可选项。字符串(String)。指定要被加载的HTML文档的 URL 地址。假如无指定值&#xff0c;则 about:blank 的新窗口会被显示。 sName : 可选项。字符串(String)。 指定打开的窗口…

mysql myisam/innodb高并发优化经验_MySQL MyISAM / PHP 高并发优化经验

最近做的一个应用&#xff0c;功能要求非常简单&#xff0c;就是 key/value 形式的存储&#xff0c;简单的 INSERT/SELECT&#xff0c;没有任何复杂查询&#xff0c;唯一的问题是量非常大&#xff0c;如果目前投入使用&#xff0c;初期的单表 insert 频率约 20Hz(次/秒&#xf…

SharePoint Framework 构建你的第一个web部件(三)

&#xfeff;&#xfeff;博客地址&#xff1a;http://blog.csdn.net/FoxDave本篇接上一讲&#xff0c;我们一起来看一下如何部署和测试本地开发的web部件。在SharePoint中预览web部件SharePoint工作台在SharePoint中被承载&#xff0c;用来在开发环境预览和测试本地web部件。它…

execCommand全集

JavaScript中的execCommand介绍 execCommand方法是执行一个对当前文档&#xff0c;当前选择或者给出范围的命令。处理Html数据时常用如下格式&#xff1a;document.execCommand(sCommand[,交互方式, 动态参数]) &#xff0c;其中&#xff1a;sCommand为指令参数&#xff08;如…

mysql my.ini位置错误_解决mysql导入数据量很大导致失败及查找my.ini 位置(my.ini)在哪...

数据库数据量很大的数据库导入到本地时&#xff0c;会等很久&#xff0c;然而等很久之后还是显示失败&#xff1b;这是就要看看自己本地的没mysql是否设置了超时等待&#xff0c;如果报相关time_out这些&#xff0c;可以把mysql.ini尾部添加max_allowed_packet、interactive_ti…

poj3186 Treats for the Cows(区间)

题目链接&#xff1a;http://poj.org/problem?id3186 题意&#xff1a;第一个数是N&#xff0c;接下来N个数&#xff0c;每次只能从队列的首或者尾取出元素。 ans每次取出的值*出列的序号。求ans的最大值。 样例 &#xff1a; input&#xff1a;5 1 2 1 5 2 output&#xff1a…

php5.2.5 mysql_IIS6 下安裝 PHP5.2.5 和 MySQL5.0 及概念澄清

假設 PHP 文件夾位於 D:/php_forIIS。1&#xff0c;php.ini 一定要拷貝到 Windows 文件夾下(重要且必須)。而 PHP4.x 版本可以不用這么做。在 Windows2003 Apache2.2 PHP5.x 下&#xff0c;也不用這么做。注意 extension 的加載配置&#xff1a;extension_dir "D:/php_…

OpenCV人脸检测并把图片写成avi视频

读出某一个文件夹下“jpg”后缀的全部图片后&#xff0c;用的OpenCV自带的人脸检测检测图片中的人脸&#xff0c;调整图片的大小写成一个avi视频。 主要是要记录一下CvVideoWriter的用法和如何从文件夹中读取某一后缀的全部文件。 代码&#xff1a; #include "stdafx.h&qu…

TextArea换行 滚动条

换行"\r\n" 滚动条自动滚动到结尾document.form1.text_information.doScroll(down); 滚动条顶部坐标document.form1.text_information.scrollTop; textarea自动滚动到结尾function add_information(info)//添加信息{ document.form1.text_information.valuedocument.…

leetcode83,删除有序链表中的重复元素

Given a sorted linked list, delete all duplicates such that each element appear only once. For example, Given 1->1->2, return 1->2. Given 1->1->2->3->3, return 1->2->3. 难点就一个&#xff0c;就是要考虑到连续3个和3个以上的情况。 …

javascript各种事件

事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcElement.releaseCapture(); 事件按键 event.keyCode event.shiftKey event.altKey event.ctrlKey 事件返回值 event.returnValue 鼠标位置 event.x event.y 窗…

mysql limit asc_MySql sql优化之order by desc/asc limit M-阿里云开发者社区

Order by desc/asc limit M是我在mysql sql优化中经常遇到的一种场景&#xff0c;其优化原理也非常的简单&#xff0c;就是利用索引的有序性&#xff0c;优化器沿着索引的顺序扫描&#xff0c;在扫描到符合条件的M行数据后&#xff0c;停止扫描&#xff1b;看起来非常的简单&am…

Spring 4 官方文档学习(十)数据访问之JDBC

说明&#xff1a;未修订版&#xff0c;阅读起来极度困难 1、Spring框架JDBC的介绍 Spring JDBC - who does what? 动作Spring你定义连接参数 是打开连接是 指定SQL语句 是声明参数&#xff0c;提供参数值 是准备、执行语句是 迭代结果&#xff08;如果有&#xff09;是 操作每…

fopen -- 打开文件或者 URL

fopen (PHP 3, PHP 4, PHP 5) fopen -- 打开文件或者 URL 说明 resource fopen ( string filename, string mode [, bool use_include_path [, resource zcontext]] ) fopen() 将 filename 指定的名字资源绑定到一个流上。如果 filename 是 "scheme://..." 的格式&am…

mysql 性能状态_MySQL获取系统性能和状态_MySQL

bitsCN.comMySQL获取系统性能和状态#!/bin/ksh INTERVAL5 PREFIX$INTERVAL-sec-status touch /tmp/running RUNFILE/tmp/running my -e show global variables >> mysql-variables while test -e $RUNFILE; do file$(date %F_%I) sleep$(date %s.%N | awk "{print $…

洛谷P3392 涂国旗

P3392 涂国旗 107通过507提交题目提供者kkksc03标签难度普及-提交 讨论 题解 最新讨论 直接读字符会waWA?--为什么不对。。。跪求找错快点给钱这不就是荷兰国旗问题吗题目描述 某国法律规定&#xff0c;只要一个由N*M个小方块组成的旗帜符合如下规则&#xff0c;就是合法的…