数据结构与算法分析:你真的理解排序算法吗——计数排序(代码详解)

一、算法描述

一个会计师负责对一个小饭店的账本进行审核。每天晚上饭店打洋时,饭店主人记录白 天的总销售额,然后打印出有总额和日期的收据。这些收据存放在一个大盒子里面.每 年年终,会计师审核盒子中的这些收据,检查是否有的已经丢失。你能想象,盒子中的 收据都是无序状态的。
会计师可以按照日期降序地排列所有的收据,然后审核。另一种方法是,他找到一个当 年的空白日历,从盒子中一条接一条取出收据来,然后在日历上相应日期用X标记.一 旦盒子为空之后,会计师仅仅需要检查一下日历上哪些日子没有标记。注意第二种方法中,从来没有两个收据会相互进行比较。如果饭店已经营业了60年,并且会计师有60年的日历,如果盒子中只有5张收据,那么这个方法将会非常低效,但是有20000张收据的话,这将是一个有效的方法,即收据的数量与总日子的比例决定了方法的效率。
在前面的教程中,我们证明了基于比较的排序算法不可能好于O( n l o g n nlogn nlogn)的时间排序元素。令人惊异的是,如果能知道这些元素的更多信息,那么就会有其他的排序方法。例如,假设你被指定排序几个元素,并且告知你每一个元素的值范围都在[0,k)之间,k比n小得 多。你就能够利用这个信息,使用一个线性的排序算法,叫做计数排序。
计数排序不需要一个比较函数,是对范围固定在[0,k)的整数排序的最佳选择。即使 k 个元素的全序能够被决定以及每一个元素的值都是唯一的,这个算法也可用。例如,如果排序一系列形如 1/p(p 为整数)的小数,p 的最大值为 k,那么每个小数 1/p 能够被分配一个唯一的值 k 一 p。
因为元素的k值形式的全序关系,所以计数排序能成功进行排序。

以下是计数排序的详细过程:

一、基本思想

计数排序的基本思想是对待排序的元素进行计数,并建立一个长度等于最大元素值加上1的辅助数组(计数数组),用来存储每个元素出现的次数。然后根据计数数组的信息,依次将元素放回原始数组中的正确位置,以实现排序。

二、具体步骤

1.确定待排序列中的最大元素和最小元素:
扫描整个待排序列,找到其中的最大值和最小值。
2.统计每个元素出现的次数:
创建一个计数数组,其长度等于最大值与最小值之差加1。
遍历待排序列,对每个元素出现的次数进行统计,并将结果存储在计数数组的相应位置。
3.计算每个元素在有序排列中的位置:
对计数数组进行前缀和操作,即对每个位置的值进行累加,使其变为该位置及之前所有元素出现的总次数。这样,计数数组中的每个值就表示了对应元素在有序排列中的最后一个位置的索引。
4.根据计数数组的信息,将元素放回原始数组中的正确位置:
创建一个临时数组,用于存储排序后的结果。
从待排序列的最后一个元素开始,根据计数数组的信息,将每个元素放入临时数组的正确位置,并更新计数数组中对应元素的值(通常通过递减来实现)。
重复此过程,直到所有元素都被放入临时数组中。这个步骤是排序的核心步骤,下面是对这个步骤的详解:

详细步骤

1.从待排序列的最后一个元素开始:
我们从 ar[n-1] 开始遍历待排序列,直到 ar[0]。这样做是为了在 countArray 中从后往前减少计数,从而避免覆盖之前已经放置在 tempArray 中的元素。
2.根据 countArray 的信息:
对于当前遍历到的元素 ar[i],我们查看 countArray[ar[i]] 的值,它表示元素 ar[i] 在排序后的序列中应该出现的次数。
3.将元素放入 tempArray 的正确位置:
由于我们已经从后往前遍历了 ar,并且每次都将元素放置在 tempArray 的当前索引位置(初始为 n-1,每次递减),因此我们可以直接将 ar[i] 放置在 tempArray 的当前索引位置,并递减该索引。
但是,由于我们实际上是通过 countArray 来指导放置的,我们并不直接操作索引,而是通过 countArray[ar[i]] 的值来决定放置多少个 ar[i]。
4.更新 countArray 的值:
在将元素放置到 tempArray 后,我们需要更新 countArray[ar[i]] 的值,以反映该元素在 tempArray 中已经被放置了多少次。
由于我们是逐个放置元素的,因此每次放置后,我们将 countArray[ar[i]] 的值减1。
这确保了当我们再次遇到相同的元素时,它会被放置在 tempArray 中的下一个正确位置。

实际操作

在实际操作中,这个过程通常是通过一个内部循环来实现的,该循环根据 countArray[ar[i]] 的值来决定在当前元素 ar[i] 上循环多少次,并在每次循环中将 ar[i] 放置到 tempArray 的当前索引位置,并递减 countArray[ar[i]] 和 tempArray 的索引。
但是,由于计数排序的特性,我们实际上并不需要一个显式的索引来遍历 tempArray,因为 countArray 已经为我们提供了每个元素应该出现的次数和位置信息。因此,我们可以直接从 countArray 的值中递减,并将元素放置到 tempArray 的“逻辑上”的正确位置(即使我们没有显式地跟踪这个位置)。

5.将排序好的结果复制回原始数组(如果需要):
如果不需要保留原始数组的内容,可以直接将排序后的结果存储在原始数组中。
否则,需要将临时数组中的排序结果复制回原始数组。

三、示例

假设待排序列为 {4, 2, 2, 8, 3, 3, 1},则计数排序的过程如下:

1.找到最大值8和最小值1。

2.创建计数数组 countArray,长度为 8 - 1 + 1 = 8,并初始化为0。

3.统计每个元素出现的次数,得到 countArray = [1, 2, 2, 2, 0, 0, 0, 1]。

4.对 countArray 进行前缀和操作,得到 countArray = [1, 3, 5, 7, 7, 7, 7, 8]。

5.创建临时数组 tempArray,长度为待排序列的长度。

6.从待排序列的最后一个元素开始,根据 countArray 的信息,将每个元素放入 tempArray 的正确位置,并更新 countArray 的值。下面是个示例:
假设 ar = [4, 2, 2, 8, 3, 3, 1],并且我们已经计算出了 countArray = [1, 2, 2, 2, 0, 0, 0, 1](注意,这里为了简化,我假设了元素范围是从0到7加上一个额外的8,所以 countArray 的长度是9)。
在放置元素时,我们会从 ar 的最后一个元素开始,即 1,并查看 countArray[1] 的值(它是2),然后将两个 1 放置到 tempArray 的末尾(逻辑上,因为我们实际上是从后往前填充的),并递减 countArray[1] 的值。接着,我们继续对 ar 中的下一个元素进行相同的操作,直到处理完所有元素。

7.得到排序后的结果 tempArray = [1, 2, 2, 3, 3, 4, 8]。

8.将排序好的结果复制回原始数组(如果需要)。

二、复杂度分析

在这里插入图片描述
时间复杂度:计数排序的时间复杂度为 O(n+k),其中 n 是待排序数组的长度,k 是待排序数组中元素的范围。
空间复杂度:计数排序的空间复杂度也为 O(n+k),因为需要创建一个长度为 k 的计数数组和一个长度为 n 的临时数组(或直接在原始数组上进行操作以避免额外的空间开销)。

三、适用情况

稳定性:计数排序是稳定的排序算法,因为两个相等的元素在排序后的序列中的相对位置和它们在原始序列中的相对位置相同。
适用场景:计数排序特别适用于待排序元素为整数且范围较小的情况。如果待排序的元素不满足这个要求(例如元素不是整数或范围很大),则需要考虑其他排序算法或进行额外的预处理步骤(如映射转换)。

四、算法实现

下面是计数排序算法的实现:

#include<stdio.h>
#include<stdlib.h>//排序ar中的n个元素,范围是[0,k)
int countingSort(int* ar, int n, int k)
{int i;int idx = 0;int* B =(int*) calloc(k,sizeof(int));//实际完成对位置的排序for (i = 0;i < n;i++){B[ar[i]]++;}//填充排序好的位置for (i = 0;i < k;i++){while (B[i]-- > 0){ar[idx++] = i;}}free(B);return 0;
}int main() {int ar[] = { 4, 2, 2, 8, 3, 3, 1 };int n = sizeof(ar) / sizeof(ar[0]);int k = 9; // 假设元素范围在[0, 9)之间  printf("Original array:\n");for (int i = 0; i < n; i++) {printf("%d ", ar[i]);}printf("\n");// 调用countingSort函数  if (countingSort(ar, n, k) != 0) {fprintf(stderr, "Sorting failed\n");return 1;}printf("Sorted array:\n");for (int i = 0; i < n; i++) {printf("%d ", ar[i]);}printf("\n");return 0;
}

五、算法优化

计数排序对整个数组进行了两次遍历。第一次处理输入数列中的所有 n 个元素。在第二次遍历时,内部的 while 循环将会执行 B[i]次,对于每一个 0<=i<k;因此表达式 ar[idx++]恰好执行 n 次。这个是实现中的关键表达式执行了 2*n 次,结果总的情能是 O(n)的。
因为待排序的数组中的元素必须是从有限的 k 个元素中得到,计数排序的使用受到了限制。我们现在讨论桶排序,这个排序方法克服了这个限制,每个元素都能够映射到一个桶中。

六、引用及参考文献

1.《算法设计手册》

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

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

相关文章

Java.6--多态-设计模式-抽象父类-抽象方法

一、多态 1.定义--什么是多态&#xff1f; a.同一个父类的不同子类对象&#xff0c;在做同一行为的时候&#xff0c;有不同的表现形式&#xff0c;这就是多态。&#xff08;总结为&#xff1a;一个父类下的不同子类&#xff0c;同一行为&#xff0c;不同表现形式。&#xff0…

【力扣】GO解决子序列相关问题

文章目录 一、引言二、动态规划方法论深度提炼子序列问题的通用解法模式 三、通用方法论应用示例&#xff1a;最长递增子序列&#xff08;LeetCode题目300&#xff09;Go 语言代码实现 四、最长连续递增序列&#xff08;LeetCode题目674&#xff09;Go 语言代码实现 五、最长重…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括&#xff1a;1. **反编译**: 反编译是将已编译的.NET程序集&#xff08;如.dll或.exe文件&#xff09;转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…

手机淘宝自动下单退货自动化RPA脚本机器人

使用手机集线器连接多个手机并发运行。 脚本分3个部分&#xff08;读取本地连接下单&#xff0c;退货获取退货地址信息&#xff0c;填写快递单号&#xff09; 脚本部分图结构看下面的图片 部分数据统计展示

基于vue框架的的高校设备信息管理系统的设计与实现tx6d7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;设备管理员,设备维护员,设备类别,设备,设备入库,设备分发,设备调拨,定期维护,维护任务,设备运行记录 开题报告内容 基于Vue框架的高校设备信息管理系统的设计与实现开题报告 一、项目背景及意义 随着高校教育事业的蓬勃发展&#xff…

线性代数学习

1.标量由只有一个元素的张量表示 import torchx torch.tensor([3,0]) y torch.tensor([2,0])x y, x * y, x / y, x**y 2.可以将向量视为标量值组成的列表 x torch.arange(4) x 3.通过张量的索引访问任一元素 x[3] 4.访问张量长度 len(x) 5.只有一个轴的张量&#xff0c…

gin入门教程(10):实现jwt认证

使用 github.com/golang-jwt/jwt 实现 JWT&#xff08;JSON Web Token&#xff09;可以有效地进行用户身份验证,这个功能往往在接口前后端分离的应用中经常用到。以下是一个基本的示例&#xff0c;演示如何在 Gin 框架中实现 JWT 认证。 目录结构 /hello-gin │ ├── cmd/ …

Could not retrieve mirrorlist http://mirrorlist.centos.org错误解决方法

文章目录 背景解决方法 背景 今天在一台新服务器上安装nginx&#xff0c;在这个过程中需要安装相关依赖&#xff0c;在使用yum install命令时&#xff0c;发生了以下报错内容&#xff1a; Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx8…

【永中软件-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

第二十九篇:TCP的报文格式,TCP系列三

TCP是协议&#xff0c;是协议就有协议格式。 1.源端口和目的端口 TCP源端口&#xff08;Source Port&#xff09;&#xff1a;源计算机上的应用程序的端口号&#xff0c;占 16 位。 TCP目的端口&#xff08;Destination Port&#xff09;&#xff1a;目标计算机的应用程序端口…

政安晨【零基础玩转各类开源AI项目】基于本地Ubuntu (Linux ) 系统应用Gradio-Lite:无服务器 Gradio 完全在浏览器中运行

目录 简介 什么是@gradio/lite? 入门 1.导入 JS 和 CSS 2. 创建标签 3. 在标签内编写你的 Gradio 应用程序 更多示例:添加其他文件和要求 多个文件 其他要求 SharedWorker 模式 代码和演示playground 1.无服务器部署 2.低延迟 3. 隐私和安全 限制 尝试一下!…

基于Python+SQL Server2008实现(GUI)快递管理系统

快递业务管理系统的设计与实现 摘要: 着网络新零售的到来&#xff0c;传统物流在网购的洗礼下迅速蜕变&#xff0c;在这场以互联网为基础的时代变革中&#xff0c;哪家企业能率先转变其工作模式就能最先分得一杯羹&#xff0c;物流管理也不例外。传统的物流管理模式效率低下&a…

同城分类信息网站源码系统 PHP+MySQL组合开发 带完整的安装代码包以及搭建部署教程

系统概述 该系统采用PHP作为后端开发语言&#xff0c;MySQL作为数据库管理系统。PHP是一种广泛使用的开源脚本语言&#xff0c;特别适合于Web开发&#xff0c;具有跨平台、易于学习、性能稳定等优点。MySQL则是一款轻量级的关系型数据库管理系统&#xff0c;具有体积小、速度快…

DHorse v1.6.0 发布,基于 k8s 的发布平台

版本说明 新增特性 支持Codeup&#xff08;阿里云云效&#xff09;代码仓库&#xff1b;支持环境的自动部署&#xff1b; 优化特性 管理员角色部署环境部需要审批&#xff1b;优化页面展示&#xff1b; 升级指南 升级指南 DHorse介绍 DHorse是一个轻量级、简单易用的云…

微服务网关Zuul

一、Zuul简介 Zuul是Netflix开源的微服务网关&#xff0c;包含对请求的路由和过滤两个主要功能。 1&#xff09;路由功能&#xff1a;负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础。 2&#xff09;过滤功能&#xff1a;负责对请求的过程…

crc, md5 和 sha的区别

效率不同: 直接看代码 import zlib import hashlib import timewith open(rD:\data., rb) as f:x f.read()s time.time() for i in range(100000):d zlib.crc32(x) print(time.time() - s)s time.time() for i in range(100000):m hashlib.md5()m.update(x)d m.hexdige…

【CSS】纯CSS Loading动画组件

<template><div class"ai-loader-box"><!-- AI loader --><div class"ai-loader"><div class"text"><p>AI智能分析中....</p></div><div class"horizontal"><div class&quo…

idea项目搭建的四种方式: 一(以idea2017为例)

目录 1. 普通java项目 2. 普通JavaWEB项目 3. maven的JavaWEB项目 4. maven的java项目 1. 普通java项目 ①点击“Create New Project”&#xff1b; ②选择“Java”&#xff0c;选择自己安装的jdk&#xff0c;点击“Next”&#xff1b; ③填好项目名称和路径&#xff1b;…

记录一个docker volume映射目录创建文件报错问题

最近用docker-compse做中间件部署。 发现使用volume映射目录后&#xff0c;初始化时创建文件报错导致容器启动失败问题。 提示是没有访问权限。 如下&#xff1a; mariadb 10:07:26.86 INFO > mariadb 10:07:26.86 INFO > Welcome to the Bitnami mariadb contain…

CORS预检请求配置流程图 srpingboot和uniapp

首先要会判断预检请求 还是简单请求 简单请求 预检请求 #mermaid-svg-1R9nYRa7P9Pll4AK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1R9nYRa7P9Pll4AK .error-icon{fill:#552222;}#mermaid-svg-1R9nYRa7P9Pll4…