摘要

计数排序本质就是统计不同元素出现的次数,然后将元素依次从小到大放置,每个元素看统计的次数,就紧挨着放置几个同样的元素。

看似简单的处理,在算法中,会依据统计的元素次数推算出每个元素的索引位置,这就是算法的魅力。

逻辑

统计每个整数在序列中出现的次数,进而推导出每个整数在有序序列中的索引。

这是通过空间换取时间的方式。

流程

  1. 获取序列中的最大值
  2. 统计每个元素出现的次数
  3. 按照顺序进行赋值

实现

根据获取到的最大值,创建序列统计序列中的元素出现的次数。创建的序列大小是 max+1,让最大值也可以放在统计序列中。

	// 找出最大值
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
// 开辟内存空间,存储每个整数出现的次数
int[] counts = new int[1 + max];

counts 数组的索引就是序列中的元素,存放的是序列中元素出现的次数。

	// 统计每个整数出现的次数
for (int i = 0; i < array.length; i++) {
counts[array[i]]++;
}

现在就通过遍历 counts 数组排序序列中的元素,这里设置一个 index 变量,用于多次出现的元素,每放置一个元素,index 就减 1,直到 index 为 0 时,再遍历下一个索引位置。

		

	// 根据整数出现次数,对整数进行排序
int index = 0;
for (int i = 0; i < counts.length; i++) {
while (counts[i]-- > 0) {
array[index++] = i;
}
}

缺点

  • 无法对负整数进行排序,这里只能排序 0 到 max 元素的序列
  • 极其浪费内存空间
  • 是不稳定的排序

进阶

看上面实现计数排序的缺点,这里就去更改它的缺点。

可以对负整数排序,获取序列中的 min 和 max,创建计数数组的长度为 max-min。

在计数数组中,当前位置 count 加上上一个位置的 count。那么当前元素在序列中的索引就是计数数组中的 count - 1。

比较难理解的是计数数组的 count 和 array 元素的索引的对应关系。

这块可以理解,计数数组中存放着该元素的起始位置和有几个相同的元素。即类似于 range(index, count) 的情况

首先是找到序列中的 minmax

	// 找出最大值
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}

接下来就是计数排序的重点了。这里做了两次处理,第一次统计序列中元素出现的次数。第二次将统计数组中每个索引位置的数字加上前一个索引位置的数字。这就可以计算出每个元素在序列中的位置就是 array[i]-min

	// 开辟内存空间,存储次数
int[] counts = new int[max-min+1];
// 统计每个整数出现的次数
for (int i = 0; i < array.length; i++) {
counts[array[i]-min]++;
}
// 累加次数
for (int i = 1; i < counts.length; i++) {
counts[i] += counts[i-1];
}

最后将序列从后往前遍历,通过计算出的索引,依次排序。从最后开始遍历就是增加序列的稳定性

	// 从后往前遍历数组,放在有序数组中的位置
int[] newArray = new int[array.length];
for (int i = array.length - 1; i >= 0; i--) {
newArray[--counts[array[i]-min]] = array[i];
}
// 将有序数组覆盖到 array
for (int i = 0; i < newArray.length; i++) {
array[i] = newArray[i];
}

技巧点/注意点

  • 计数数组中的索引是序列元素-min
  • 从后往前遍历数组,是保证数组的稳定性,如果从头开始,相同元素在原来数组中的相对位置会发生变化
  • 计数数组中的 count 在赋值一次后要进行 -- 操作。

计数排序的精妙点就在通过两次的统计,就可以计算出每个元素在序列中的位置

时间和空间复杂度

  • 最好、最坏、平均时间复杂度:O(n+k)
  • 空间复杂度:O(n+k)
  • 属于稳定排序

k 是整数的取值范围

数据结构与算法-排序(八)计数排序(Counting Sort)的更多相关文章

  1. Hark的数据结构与算法练习之计数排序

    算法说明 计数排序属于线性排序,它的时间复杂度远远大于常用的比较排序.(计数是O(n),而比较排序不会超过O(nlog2nJ)). 其实计数排序大部分很好理解的,唯一理解起来很蛋疼的是为了保证算法稳定 ...

  2. JavaScript 数据结构与算法之美 - 桶排序、计数排序、基数排序

    1. 前言 算法为王. 想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远. 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算 ...

  3. 数据结构和算法(Golang实现)(18)排序算法-前言

    排序算法 人类的发展中,我们学会了计数,比如知道小明今天打猎的兔子的数量是多少.另外一方面,我们也需要判断,今天哪个人打猎打得多,我们需要比较. 所以,排序这个很自然的需求就出来了.比如小明打了5只兔 ...

  4. 排序算法的C语言实现(下 线性时间排序:计数排序与基数排序)

    计数排序 计数排序是一种高效的线性排序. 它通过计算一个集合中元素出现的次数来确定集合如何排序.不同于插入排序.快速排序等基于元素比较的排序,计数排序是不需要进行元素比较的,而且它的运行效率要比效率为 ...

  5. 数据结构和算法(Golang实现)(25)排序算法-快速排序

    快速排序 快速排序是一种分治策略的排序算法,是由英国计算机科学家Tony Hoare发明的, 该算法被发布在1961年的Communications of the ACM 国际计算机学会月刊. 注:A ...

  6. 数据结构和算法(Golang实现)(19)排序算法-冒泡排序

    冒泡排序 冒泡排序是大多数人学的第一种排序算法,在面试中,也是问的最多的一种,有时候还要求手写排序代码,因为比较简单. 冒泡排序属于交换类的排序算法. 一.算法介绍 现在有一堆乱序的数,比如:5 9 ...

  7. 数据结构和算法(Golang实现)(20)排序算法-选择排序

    选择排序 选择排序,一般我们指的是简单选择排序,也可以叫直接选择排序,它不像冒泡排序一样相邻地交换元素,而是通过选择最小的元素,每轮迭代只需交换一次.虽然交换次数比冒泡少很多,但效率和冒泡排序一样的糟 ...

  8. 数据结构和算法(Golang实现)(21)排序算法-插入排序

    插入排序 插入排序,一般我们指的是简单插入排序,也可以叫直接插入排序.就是说,每次把一个数插到已经排好序的数列里面形成新的排好序的数列,以此反复. 插入排序属于插入类排序算法. 除了我以外,有些人打扑 ...

  9. 数据结构和算法(Golang实现)(22)排序算法-希尔排序

    希尔排序 1959 年一个叫Donald L. Shell (March 1, 1924 – November 2, 2015)的美国人在Communications of the ACM 国际计算机 ...

  10. 数据结构和算法(Golang实现)(23)排序算法-归并排序

    归并排序 归并排序是一种分治策略的排序算法.它是一种比较特殊的排序算法,通过递归地先使每个子序列有序,再将两个有序的序列进行合并成一个有序的序列. 归并排序首先由著名的现代计算机之父John_von_ ...

随机推荐

  1. 第12次抽考(GUI)

    1. package week4; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextFiel ...

  2. PYTHON IDLE同时运行两个PY,互不影响

    1.打开指定文件夹 2.右击a1.py,用IDLE打开(idle1) 3.右击a2.py,用IDLE打开(idle2) 4.则分别按F5运行,则两个互不影响. 5.再用IDLE1打开新程序,用IDLE ...

  3. 团队开发day06

    实现功能,通过连接,获取到品牌商存入数据库的数据, 将数据返回到list集合,展示在安卓前端 出现问题:servlet响应请求返回数据格式出错,利用gson类将java类 转化为jsonobject对 ...

  4. Scala学习——函数高级操作

    scala函数高级操作 一.字符串高级操作 多行字符串和插值 package top.ruandb.scala.Course06 object StringApp { def main(args: A ...

  5. 使用deepin连接罗技k380

    1,刚开始总是连不上,连上了就断开了,以为是deepin系统的问题. 2,首先在华为论坛上找到这样的一片解决方案:https://cn.ui.vmall.com/thread-21831568-1-1 ...

  6. 网络损伤仪WANsim中的时延的不同模型

    网络损伤仪WANsim中的3种时延模型 时延指的是报文从网络的一端到达另一端所花费的时间. 网络损伤仪WANsim中为用户提供了3种时延损伤的模型.常量模型.均匀分布.正态分布. 这3种模型按照各自的 ...

  7. odoo视图 “动作” 里添加菜单按钮:案例

    ####################################################################################odoo中常用方法,在动作里面添 ...

  8. C++第三十七篇 -- 调试驱动程序

    上一篇写的KMDF程序是通过串口进行配置的,那么我们在VS中Attach to process外,可以直接用Winbdg进行调试,winbdg.exe所在路径为C:\Program Files (x8 ...

  9. Linux符号描述

    &  表示任务在后台执行,如要在后台运行redis-server,则有  redis-server &&& 表示前一条命令执行成功时,才执行后一条命令 ,如 echo ...

  10. Spring in Action学习笔记(2)

    Spring基础 AOP 面向切面编程 通知.连接点.切点.切面 Spring提供 4 种类型的AOP支持: 基于代理的经典SpringAOP:使用ProxyFactoryBean. 纯POJO切面: ...