【排序算法】计数排序引发的围观风波——一种O(n)的排序

简介: 计算机课上,老师给一串数字6 1 6 9 9 1 4 2 1 5 8 8,问道:这一串数字,你们写个程序给我看,要求效率较高。学不出来的别下课了。

前言



计算机课上,老师给一串数字6 1 6 9 9 1 4 2 1 5 8 8,问道:这一串数字,你们写个程序给我看,要求效率较高。学不出来的别下课了。


顿时场下一片哗然,但有很多小朋友硬着头皮啪啪啪的开始敲了。


老师走到pigpian身边,pigpian很难得皱了皱眉头


20200806165121666.png


很难很难得写下了下面代码:


int a[]= {6,1,6,9,9,1,4,2,1,5,8,8};
for(int i=a.length-1;i>=0;i--)
{
  for(int j=0;j<i;j++)
  {
    if(a[j]>a[j+1])
    {
      int team=a[j];
      a[j]=a[j+1];
      a[j+1]=team;
    }
  }
}
System.out.println(Arrays.toString(a));



老师:“gun吧,都2020年还用O(n2)得算法,快,快回去吃饭吧,快gun吧”。pigpian一脸无奈得走出教室,接着老师问道有没有其他人写出来,慢慢得挪到doudou得旁边。


doudou着急解释道:老师,你看我的O(nlogn)得快排算法:


int a[]= {6,1,6,9,9,1,4,2,1,5,8,8};
Arrays.sort(a);
System.out.println(Arrays.toString(a));


老师轻蔑得嘲讽道:“gun 吧,就知道投机取巧,我看你海!回去吃饭吧” 紧着着老师走到bigmao 的旁边,bigmao 给老师看了他的代码:


private static void quicksort(int [] a,int left,int right)
{
  int low=left;
  int high=right;
    //下面两句的顺序一定不能混,否则会产生数组越界!!!very important!!!
  if(low>high)
    return;
  int k=a[low];//取最左侧的一个作为衡量,最后要求左侧都比它小,右侧都比它大。
  while(low<high)
  {
    while(low<high&&a[high]>=k)
    {
      high--;
    }
    //这样就找到第一个比它小的了
    a[low]=a[high];
    while(low<high&&a[low]<=k)
    {
      low++;
    }
    a[high]=a[low];     
  }
  a[low]=k;
  quicksort(a, left, low-1);
  quicksort(a, low+1, right);   
}


老师脸角泛起微光:“不错不错,手写快排还是挺棒的,回去吃饭吧!”。

此时bigsai举起他的小手手:“老师快来,我写的这个贼快”。bigsai亮起他的代码:


int a[]= {6,1,6,9,9,1,4,2,1,5,8,8};
int count[]=new int[10];
for(int i=0;i<a.length;i++)
{
  count[a[i]]++;
}
int index=0;
for(int i=0;i<count.length;i++)
{
  while (count[i]-->0) {
    a[index++]=i;
  }
}
System.out.println(Arrays.toString(a));


"不错不错,这个方法效率确实很高,你回去把这种排序的方法和大家分享一下吧!"老师惊艳道!


bigsai出门后,站在门外的pigpiandoudou拦住问道:“sai哥这是啥东东啊”。

"计数排序。流程看图,听我下面慢慢讲:"


20200808091520863.gif


计数排序介绍



或许上面的代码你看起来还有点懵逼,但是不要紧,我们在这里给你讲明白什么是计数排序。对于计数排序,百度百科是这么说的:


计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)


对于额外数组该如何理解呢?


我们慢慢来,在以前介绍桶排序的时候,我们知道每个桶里面是可以给一个范围的数字放进去。从每个桶的实质来看可以是List集合


20200728011506731.png


但如果每个桶中只有一种元素,那么这个桶就可以不需要使用集合去储存标记,而是用一个数字即可进行标记认为它出现了多少次


20200808004159784.png


所以这种每个桶只能放一种元素的,我们不需要每个桶再用List集合去装,而用数组的值储存对应编号出现的词数即可,例如上述的a[1]=2表示其中的1号桶出现两次,而a[3]=0表示元素3没有出现过。


而这样的数值如何计算呢?


  • 很简单,对待排序目标序列遍历一次,每次遍历的值让这个值的编号加上1,说明对应元素词数加一。例如上述第一个1就a[1]++,第二个5就a[5]++……
  • 然后取值时候遍历这个数字,顺序将目标编号的数字取出来即可。(每取一个对应位置数值减1直到为0为止)。例如上述遍历这个数组,就获得1 1 2 4 4 5这个序列。你看看,这个时间复杂度是不是O(n)的?


上面算法设计就很好了嘛?当然不是,如果是1,2 ,3之类数据肯定没啥问题,但是如果1000001,1000002,1000003之类的序列你这么开数组不是太多空间了?并且前面也要遍历很多无用的次数。


所以我们在设计具体算法的时候,先找到最小值min,再找最大值max。然后创建这个区间大小的数组,从min的位置开始计数,这样就可以最大程度的压缩空间,提高空间的使用效率。


20200808011111756.png


代码实现



通过上述分析,计数排序的实现代码为:


import java.util.Arrays;
public class test {
  public static void jishusort(int a[])
  {
    int min=Integer.MAX_VALUE;int max=Integer.MIN_VALUE;
    for(int i=0;i<a.length;i++)//找到max和min
    {
      if(a[i]<min) 
        min=a[i];
      if(a[i]>max)
        max=a[i];
    }
    int count[]=new int[max-min+1];//对元素进行计数
    for(int i=0;i<a.length;i++)
    {
      count[a[i]-min]++;
    }
    //排序取值
    int index=0;
    for(int i=0;i<count.length;i++)
    {
      while (count[i]-->0) {
        a[index++]=i+min;//有min才是真正值
      }
    }
  }
  public static void main(String[] args) {
    int a[]= {6,1,6,9,9,1,4,2,1,5,8,8};
    jishusort(a);
    System.out.println(Arrays.toString(a));
  }
}


打印结果为:

[1, 1, 1, 2, 4, 5, 6, 6, 8, 8, 9, 9]


结语



通过上面我想计数排序你已经搞得很清楚了,计数排序的时间复杂度为O(n+k)其中k为正数范围;线性时间大部分都比其他排序快一点,但是也不一定,例如你遇到1 2 4 2 100001这样一个序列,其中k的范围为10000,虽然他是O(n+k)=O(k)k远大于n情况,但是此时O(k)>O(nlogn)因为n太小,而K太大,需要遍历的词数太多了。


所以即使计数排序它是线性但是并非所有情况都是最好的方法,并且也占用了太多内存。当数据范围波动不是很大,数据相对比较集中,这时候用计数排序肯定是最好的啦,这点和桶排序的要求很像哦,没错,它其实就是一种特殊的桶排序,他的桶大小为1,用数值计数词数而以,其他都是一样的操作。


此时bigsai沾沾自喜终于讲完了,在旁边的pigpian和doudou直呼:讲的真的太好了,我不光要把它收藏下来,我还要给你点赞!


bigsai笑了,心想光点赞哪里够🤭,阴森森的冲着两个少女……说到:点赞收藏哪里够,公众号也顺便一波:bigsai 关注一波,后面还会讲其他的。


目录
相关文章
|
4月前
|
算法
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
94 8
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
106 7
|
2月前
|
搜索推荐 Shell
解析排序算法:十大排序方法的工作原理与性能比较
解析排序算法:十大排序方法的工作原理与性能比较
73 9
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
38 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
搜索推荐 Java Go
深入了解计数排序算法
深入了解计数排序算法
44 4
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
34 0
|
2月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
79 0
|
4月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
52 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
4月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
46 0
下一篇
DataWorks