首页 > 编程语言 >算法与数据结构——计数排序

算法与数据结构——计数排序

时间:2024-10-29 10:13:16浏览次数:6  
标签:排序 nums int counter 计数 num 数组 数据结构

计数排序

计数排序(counting sort)通过统计元素数量来实现排序,通常应用于整数数组。

简单实现

给定一个长度为n的数组nums,其中的元素都是“非负整数”,计数排序的整体流程如下:

  1. 遍历数组,找出其中最大的数组,记为m,然后创建一个长度为 m+1 的辅助数组counter
  2. 借助counter统计nums中各数字的出现次数,其中counter[num]对应数字num的出现次数。统计方法很简单,只需遍历nums,每轮将counter[num]增加1即可。
  3. 由于counter的各个索引天然有序,因此相当于所有数字已经排好序了。接下来,遍历counter,根据各数组出现次数从小到大的顺序填入nums即可。

/* 计数排序 */
// 简单排序,无法用于排序对象
void countingSortNative(vector<int> &nums){
	// 1. 统计数组最大元素 m
	int m = 0;
	for (int num : nums){
		m = m > num ? m : num;
	}
	// 2. 统计各数字的出现次数
	// counter[num] 代表 num 的出现次数
	vector<int> counter(m + 1);
	for (int num : nums){
		counter[num]++;
	}
	// 3. 遍历 counter ,将各元素填入原数组 nums
	int i = 0;
	for (int num = 0; num < m + 1; num++){
		for (int j = 0; j < counter[num]; j++, i++){
			nums[i] = num;
		}
	}
}

计数排序与桶排序的联系
从桶排序的角度看,我们可以将计数数组counter的每一个索引视为一个桶,将统计数量的过程看作各个元素分配到对应的桶中。本质上,计数排序是桶排序在整数类型数据下的一个特例。

完整实现

当输入数据是对象时,上述步骤3.就失效了。假设输入数据时商品对象,我们想按照商品价格(类的成员变量)对商品进行;排序,而上述算法只能给出价格的排序结果。

如何才能得到原数据的排序结果呢?我们首先计算counter的“前缀和”。顾名思义,索引i处的前缀和prefix[i]等于数组前i个元素之和:

前缀和具有明确的意义,prefix[num] - 1代表元素num在结果数组res中最后一次出现的索引。这个信息非常关键,因为它告诉我们各个元素应该出现在结果数组的哪个位置。接下来,我们倒序遍历原数组nums的每个元素num,在每轮迭代中执行以下两步。

  1. num填入数组res的索引prefix[num] - 1处。
  2. 令前缀和prefix[num]减小1,从而得到下次放置num的索引。

遍历完成后,数组res中就是排序好的结果,最后使用res覆盖原数组nums即可。

/* 计数排序 */
// 完整实现,可排序对象,并且是稳定排序
void countingSort(vector<int> &nums){
	// 1. 统计数组最大元素
	int m = 0;
	for (int num : nums){
		m = m > num ? m : num;
	}
	// 2. 统计各个数字的出现次数
	// counter[num] 代表num的出现次数
	vector<int> counter(m + 1);
	for (int num : nums){
		counter[num]++;
	}
	// 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引”
	// 即 counter[num]-1 是 num 在 res 中最后一次出现的索引
	for (int i = 0; i < counter.size() - 1; ++i){
		counter[i + 1] += counter[i];
	}
	// 4. 倒序遍历 nums ,将各元素填入结果数组 res
	// 初始化数组 res 用于记录结果
	int n = nums.size();
	vector<int> res(n);
	for (int i = n - 1; i >= 0; --i){
		int num = nums[i];
		res[counter[num] - 1] = num;
		counter[num]--;
	}
	nums = res;
}

算法特性

相关文章

  • antdesign vue 步骤条a-step按审核人员节点排序显示逻辑
    一、需求内容目前审核人员角色有:学术、法务、售后,串行执行审核流程。审核流程:发起/修改审核-》审核节点审核节点规则:学术-》法务-》售后,每个节点均可以审核或修改。审核状态:发起、修改、待审核、已审核。因此前端根据节点规则来展示审核步骤给用户。二......
  • 【数据结构与算法】图(Graph)
    文章目录图的逻辑结构一.图的定义二.图的基本概念和术语图的存储结构一.邻接矩阵(数组)二.邻接表(链式)三.十字链表四.邻接多重表五.边集数组图的遍历一.深度优先遍历二.广度优先遍历三.图的遍历与图的连通性图的逻辑结构在线性表中,数据元素之间是被串起来的,仅有线......
  • (1)程序设计与数据结构连续剧
    《程序设计与数据结构》C程序基本构成、变量定义与变量名规则C程序基本构成示例#include<stdio.h>//预处理指令,包含标准输入输出头文件//全局变量声明intglobal_variable=10;//函数原型声明intadd(inta,intb);intmain(){//局部变量定义......
  • DICOM 基础知识:深入理解DICOM数据结构与标签说明
    目录DICOM图像概念DICOM图像关键特性:DICOM文件结构常见数据元素:数据元素示例详解DICOM-VR数据类型说明DICOM标准支持的数据集结语     DICOM图像概念        DICOM(DigitalImagingandCommunicationsinMedicine)是一种用于存储、传输和处......
  • 数据结构与算法——树与二叉树
    树与二叉树1.树的定义与相关概念树的示例:树的集合形式定义Tree=(K,R)元素集合:K={ki|0<=i<=n,n>=0,ki∈ElemType}(n为树中结点数,n=0则树为空,n>0则为非空树)对于一棵非空树,关系R满足下列条件:1.有且仅有一个结点没有前驱,称为根结点。2.处根结点外,其余每个结点有且仅有一个前......
  • 算法学习笔记1:数据结构
    数据结构并查集一种树形的数据结构,近乎O(1)的时间复杂度。又一次理解了并查集用来维护额外信息的作用,可以用来记录集合中的元素个数,也可以维护节点到根节点之间的距离,可能还有别的,然后在进行路径压缩的时候修改需要维护的额外信息。主要构成pre[]数组、find()、join()......
  • 《练习题011:阶乘-递归-反向输出-排序-逆序(共9种)》
    《目录》01:阶乘求和02:递归求阶乘03:递归输出04:反向输出05:反向输出II06:设置输出颜色07:算素数08:排序09:逆序列表01:阶乘求和题目求1+2!+3!+…+20!的和。程序分析1+2!+3!+…+20!=1+2(1+3(1+4(…20(1))))res=1foriinrange(20,1,-1):res=i*res+1......
  • 必学排序算法——堆排序
    目录前言一、什么是堆排序二、堆排序算法的主要步骤三、算法特性四、算法优缺点五、应用场景六、堆排序算法动态图解七、c++代码模板八、经典例题1.排序数组代码题解九、结语前言堆排序算法是必须掌握的一种基础算法,在一些比较出名的竞赛acm、蓝桥杯,并且在一些公司......
  • 数据结构-------------二叉树后续(单链表实现二叉树)
    1:前中后序遍历在用链表实现二叉树的时候我们要首先了解一下二叉树的遍历,让我们来看一下二叉树都有那些遍历1.1 遍历规则按照二叉树的遍历规则遍历有:前序.中序.后序。(1)前序:首先访问根节点再去访问左右子树(访问顺序为:根结点、左⼦树......
  • 【数据结构与算法】《Java 算法宝典:探秘从排序到回溯的奇妙世界》
    目录标题:《Java算法宝典:探秘从排序到回溯的奇妙世界》一、排序算法1、冒泡排序2、选择排序3、插入排序4、快速排序5、归并排序二、查找算法1、线性查找2、二分查找三、递归算法四、动态规划五、图算法1.深度优先搜索(DFS)2.广度优先搜索(BFS)六、贪心算法七、分治算法......