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

一、算法描述

一个会计师负责对一个小饭店的账本进行审核。每天晚上饭店打洋时,饭店主人记录白 天的总销售额,然后打印出有总额和日期的收据。这些收据存放在一个大盒子里面.每 年年终,会计师审核盒子中的这些收据,检查是否有的已经丢失。你能想象,盒子中的 收据都是无序状态的。
会计师可以按照日期降序地排列所有的收据,然后审核。另一种方法是,他找到一个当 年的空白日历,从盒子中一条接一条取出收据来,然后在日历上相应日期用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 语言代码实现 五、最长重…

RHCE-web篇

一.web服务器 Web 服务器是一种软件或硬件系统&#xff0c;用于接收、处理和响应来自客户端&#xff08;通常是浏览器&#xff09;的 HTTP 请求。它的主要功能是存储和提供网站内容&#xff0c;比如 HTML 页面、图像、视频等。 Web 服务器的主要功能 处理请求&#xf…

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

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

TCP 攻击为何在 DDoS 攻击中如此常见

分布式拒绝服务攻击&#xff08;Distributed Denial of Service, DDoS&#xff09;是一种常见的网络攻击手段&#xff0c;通过大量请求使目标服务器过载&#xff0c;导致合法用户无法访问服务。在众多 DDoS 攻击类型中&#xff0c;TCP 攻击尤为常见。本文将探讨 TCP 攻击在 DDo…

MySQL8 配置密码和用户创建及授权详解:Java开发最佳实践

全文目录&#xff1a; 开篇语&#x1f4cc; 目录&#x1f31f; 前言&#x1f4dd; 摘要&#x1f4da; 简介&#x1f50d; 概述&#x1f9e9; 核心源码解读1️⃣ 加载驱动2️⃣ 建立数据库连接3️⃣ 用户创建4️⃣ 配置权限5️⃣ 刷新权限 &#x1f4bb; 案例分析&#x1f310; …

Spring Boot植物健康系统:绿色科技的创新引擎

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了植物健康系统的开发全过程。通过分析植物健康系统管理的不足&#xff0c;创建了一个计算机管理植物健康系统的方案。文章介绍了植物健康系统的系统分析部分&…

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

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

Python中正则表达式的使用

下面介绍正则表达式的基础知识和使用方法。 正则表达式是什么? 正则表达式(Regular Expression&#xff0c;简称regex)是一种用于匹配字符串模式的强大工具。就像是一种特殊的搜索语言。 Python中使用正则表达式的基本步骤&#xff1a; import re # 首先导入re模块# 基本使…

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

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

VuePress的基本常识

今天大概了解了一下Vuepress&#xff0c;感觉很棒&#xff0c;看着极其简单&#xff0c;自己也想做一个&#xff0c;后续我大概率也会做一个用Vuepress为基础做的博客网站&#xff0c;很酷~ 哈哈哈&#xff0c;下面是我今天学习Vuepress的一些内容&#xff0c;简单分享下&#…

基础巩固:

发布订阅模式的实现&#xff1a;&#xff08;基于观察者模式实现&#xff09; 发布&#xff1a;&#xff08;观察者&#xff09;发布者向被观察类中注册感兴趣的事件&#xff0c;以map为结构&#xff08;key值存储感兴趣的事件&#xff0c;value为该事件的感兴趣对象&#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/ …

[LeetCode] 50. Pow(x, n)

题目描述&#xff1a; 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000示例 2&#xff1a; 输入&#xff1a;x 2.10000, n 3 输出…

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…

负载均衡详解:背景、实现技术、作用范围与常用算法

负载均衡&#xff08;Load Balancing&#xff09;是一种通过将请求分配到多个服务器上&#xff0c;从而优化资源使用、提高响应速度并增强系统可靠性的一种技术手段。它是现代分布式系统和互联网应用中不可或缺的一部分。在本篇文章中&#xff0c;我们将深入探讨负载均衡的方方…

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

前言 由于网站注册入口容易被黑客攻击&#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;目标计算机的应用程序端口…

Vue3+Vite实现Excel表格去重

Vue3Vite实现Excel表格去重 一、需求 Excel表格列中存在重复的数据&#xff0c;现想通过插件实现去重功能&#xff0c;具体需求为&#xff1a; 选择要处理的Excel表格&#xff0c;支持.xlsx和.xls格式选择要处理的表格列对表格进行去重处理&#xff0c;去重的数据保留第一个数…