设为首页 - 加入收藏  
您的当前位置:首页 >域名 >计数排序真的不重要? 正文

计数排序真的不重要?

来源:汇智坊编辑:域名时间:2025-11-05 15:16:58

计数排序虽然不是计数面试常考题目,但是排序计数排序的求统计数组步骤和最后元素归位思想是我们刷题时经常用到的,例如原地置换,不重使用数组模拟 hashmap 等,计数所以还是排序很有必要看一下的。

今天我们就一起来看看线性排序里的不重计数排序到底是怎么回事吧。

我们将镜头切到袁记菜馆

因为今年袁记菜馆的计数效益不错,所以袁厨就想给员工发些小福利,排序让小二根据员工工龄进行排序,不重但是计数菜馆共有 100000 名员工,菜馆开业 10 年,排序员工工龄从 0 - 10 不等。不重

看来这真是计数一个艰巨的任务啊。

当然我们可以借助之前说过的排序 归并排序 和 快速排序 解决,但是不重我们有没有其他更好的方法呢?

了解排序算法的老哥可能已经猜到今天写什么啦。是滴,亿华云计算我们今天来写写用空间换时间的线性排序。

说之前我们先来回顾一下之前的排序算法,最好的时间复杂度为 O(nlogn) ,且都基于元素之间的比较来进行排序。

我们来说一下非基于元素比较的排序算法,且时间复杂度为 O(n),时间复杂度是线性的,所以我们称其为线性排序算法。

其优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k),此时的 k 则代表整数的范围。快于任何一种比较类排序算法,不过也是需要牺牲一些空间来换取时间。

下面我们先来看看什么是计数排序,这个计数的含义是什么?

我们假设某一分店共有 10 名员工,

工龄分别为 1,2,3,5,IT技术网0,2,2,4,5,9

那么我们将其存在一个长度为 10 的数组里,,但是我们注意,我们数组此时存的并不是元素值,而是元素的个数。见下图

注:此时我们这里统计次数的数组长度根据最大值来决定,上面的例子中最大值为 9 ,则长度为 9 + 1 = 10。暂且先这样理解,后面会对其优化 。

我们继续以上图的例子来说明,在该数组中,索引代表的为元素值(也就是上面例子中的工龄),数组的值代表的则是云服务器提供商元素个数(也就是不同工龄出现的次数)。

即工龄为 0 的员工有 1 个, 工龄为 1 的员工有 1 个,工龄为 2 的员工有 3 个 。。。

然后我们根据出现次数将其依次取出看看是什么效果。

0,1,2,2,2,3,4,5,5,9

我们发现此时元素则变成了有序的,但是这并不是排序,只是简单的按照统计数组的下标,输出了元素值,并没有真正的给原始数组进行排序。

这样操作之后我们不知道工龄属于哪个员工。

见下图

举例

虽然喵哥和杰哥工龄相同,如果我们按照上面的操作输出之后,我们不能知道工龄为 4 的两个员工,哪个是喵哥哪个是杰哥。

所以我们需要借助其他方法来对元素进行排序。

大家还记不记得我们之前说过的前缀和,下面我们通过上面统计次数的数组求出其前缀和数组。

因为我们是通过统计次数的数组得到了前缀和数组,那么我们来分析一下 presum 数组里面值的含义。

例如我们的 presum[2] = 5 ,代表的则是原数组小于等于 2 的值共有 5 个。presum[4] = 7 代表小于等于 4 的元素共有 7 个。

是不是感觉计数排序的含义要慢慢显现出来啦。

其实到这里我们已经可以理解的差不多了,还差最后一步,

此时我们要从后往前遍历原始数组,然后将遍历到的元素放到临时数组的合适位置,并修改 presum 数组的值,遍历结束后则达到了排序的目的。

这时有人要问了,为什么我们要从后往前遍历呢?

这个问题的答案,我们等下说,继续往下看吧。

计数排序

我们从后往前遍历,nums[9] = 9,则我们拿该值去 presum 数组中查找,发现 presum[nums[9]] = presum[9] = 10 ,

大家还记得我们 presum 数组里面每个值的含义吗,我们此时 presum[9] = 10,则代表在数组中,小于等于的数共有 10 个,则我们要将他排在临时数组的第 10 个位置,也就是 temp[9] = 9。

我们还需要干什么呢?我们想一下,我们已经把 9 放入到 temp 数组里了,已经对其排好序了,那么我们的 presum 数组则不应该再统计他了,则将相应的位置减 1 即可,也就是 presum[9] = 10 - 1 = 9;

下面我们继续遍历 5 ,然后同样执行上诉步骤

我们继续查询 presum 数组,发现 presum[5] = 9,则说明小于等于 5 的数共有 9 个,我们将其放入到 temp 数组的第 9 个位置,也就是

temp[8] = 5。然后再将 presum[5] 减 1 。

是不是到这里就理解了计数排序的大致思路啦。

那么我们为什么需要从后往前遍历呢?我们思考一下,如果我们从前往后遍历,相同元素的话,前面的元素则会先归位再减一,这样则会使计数排序变成不稳定的排序算法。

这个排序的过程像不像查字典呢?通过查询 presum 数组,得出自己应该排在临时数组的第几位。然后再修改下字典,直到遍历结束。

那么我们先来用动画模拟一下我们这个 bug 版的计数排序,加深理解。

注:我们得到 presum 数组的过程在动画中省略。直接模拟排序过程。

但是到现在就完了吗?显然没有,我们思考下这个情况。

假如我们的数字为 90,93,94,91,92 如果我们根据上面方法设置 presum 数组的长度,那我们则需要设置数组长度为 95(因为最大值是94),这样显然是不合理的,会浪费掉很多空间。

还有就是当我们需要对负数进行排序时同样会出现问题,因为我们求次数的时候是根据 nums[index] 的值来填充 presum 数组的,所以当 nums[index] 为负数时,填充 presum 数组时则会报错。

此时通过最大值来定义数组长度也不合理。

所以我们需要采取别的方法来定义数组长度。

下面我们来说一下偏移量的概念。

例如 90,93,94,91,92,我们 可以通过 max ,min 的值来设置数组长度即 94 - 90 + 1 = 5 。偏移量则为 min 值,也就是 90。那么我们的 90 则对应索引 0 。

见下图。

这样我们填充 presum 数组时就不会出现浪费空间的情况了,负数?出现负数的情况当然也可以。继续看

例如:-1,-3,0,2,1

一样可以,哦了,到这里我们就搞定了计数排序,下面我们来看一哈代码吧。

class Solution {     public int[] sortArray(int[] nums) {         int len = nums.length;         if (nums.length < 1) {              return nums;         }         //求出最大最小值         int max = nums[0];         int min = nums[0];         for (int x : nums) {             if (max < x)  max = x;                    if (min > x)  min = x;                  }         //设置 presum 数组长度,然后求出我们的前缀和数组,         //这里我们可以把求次数数组和前缀和数组用一个数组处理         int[] presum = new int[max-min+1];         for (int x : nums) {             presum[x-min]++;         }         for (int i = 1; i < presum.length; ++i) {             presum[i] = presum[i-1]+presum[i];          }         //临时数组         int[] temp = new int[len];         //遍历数组,开始排序,注意偏移量         for (int i = len-1; i >= 0; --i) {             //查找 presum 字典,然后将其放到临时数组,注意偏移度             int index = presum[nums[i]-min]-1;             temp[index] = nums[i];             //相应位置减一             presum[nums[i]-min]--;                    }         //copy回原数组         System.arraycopy(temp,0,nums,0,len);         return nums;     } } 

好啦,这个排序算法我们已经搞定了,下面我们来扒一扒它。

计数排序时间复杂度分析

我们的总体运算量为 n+n+k+n ,总体运算是 3n + k 所以时间复杂度为 O(N+K);

计数排序空间复杂度分析

我们用到了辅助数组,空间复杂度为 O(n)

计数排序稳定性分析

稳定性在我们最后存入临时数组时有体现,我们当时让其放入临时数组的合适位置,并减一,所以某元素前面的相同元素,在临时数组,仍然在其前面。所以计数排序是稳定的排序算法。

虽然计数排序效率不错但是用到的并不多。

这是因为其当数组元素的范围太大时,并不适合计数排序,不仅浪费时间,效率还会大大降低。 当待排序的元素非整数时,也不适用,大家思考一下这是为什么呢?

好啦,今天的文章就到这啦,我们下期再见,拜了个拜

上一篇:使用苹果U盘启动电脑装Ghost版系统教程(简单快捷的安装方法,让你轻松拥有Ghost版系统)
下一篇:关于iptables有价值的信息很多,但是大多都描述的很复杂。假如你想做些基本的配置,下面的 How To 很适合你。 # iptables -L 列出您当前iptables中在规则。假如您是刚刚建立您的服务器,那么可能此时还没有任何规则,而且您应该看到如下: Chain INPUT (policy ACCEPT) Chain FORWARD (policy ACCEPT) Chain OUTPUT (policy ACCEPT) ◆ 允许建立会话 我们可以允许建立会话来接受流量: # iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT ◆ 在指定端口上允许入站流量 阻断所有流量您也可以启动系统,但是您可能正在通过SSH工作,所有在您阻断其他流量前有必要允许SSH流量。 为了在22端口号(默认的SSH端口)上的允许流量入站,您可以告诉iptables允许您的网卡接受所有的目的端口为22的TCP流量。 # iptables -A INPUT -p tcp -I eth0 --dport ssh -j ACCEPT 特别的,这将向表中追加(-A)INPUT规则,允许目的端口号为SSH的所有流量进入接口(-i) eth0,以便iptables完成跳转(-j)或动作:ACCEPT 让我们核对下这些规则:(这里仅显示了少数行,您应该看到更多) # iptables -L 现在,让我们允许所有的web流量 # iptables -A INPUT -p tcp -I eth0 --dport 80 -j ACCEPT 检查我们现有的规则 # iptables -L 我们已经指定SSH和web端口为允许通过的TCP流量,但是因为我们还没阻断任何流量,所以到目前为止所有的流量仍然可以进入。 ◆ 阻断流量 一旦一条规则对一个包进行了匹配,其他规则不再对这个包有效。因为我们的规则首先允许SSH和WEB流量,所以只要我们阻断所有流量的规则紧跟其後,我们依然能接受我们感兴趣的流量。大家要做的仅仅是把阻断所有流量的规则放在最後,所以我们需要再次用到它。 # iptables -A INPUT -j DROP 因为我们刚才没有指定一个接口或一个协议,所以除了web和ssh流量外其他任何流量都会被阻断。 ◆ 编辑 iptables 到目前为止我们设置过程中唯一的问题是回环端口(loopbakc)也被阻断了。我们本可以通过指定 -I eth0 来仅仅丢弃eth0上的数据包,但我们也可以为回环端口(loopback)添加一条规则。假如我们追加这条规则,这将太晚了----因为所有的流量已经 被丢弃。我们必须插入这条跪着到第4行。 # iptables -I INPUT 4 -I lo -j ACCEPT 最後2行看起来几乎一样,因此我们可以让iptables列的更详细些。 # iptables -L -v ◆ 日志记录 在上面的例子中,所有的流量都不会被记录。假如您愿意在syslog中记录被丢弃的包, 下面将是最快捷的方式: # iptables -I INPUT 5 -m limit --limit 5/min -j LOG --log-prefix iptables denied: --log-level 7 看 提示 段获得更多关于logging的ideas. ◆ 保存 iptables 假如您现在要重新启动机器的话,您的iptables配置将会消失。为了不用每次重新启动时敲入这些命令,您可以保存你的配置,让它在系统启动时自动启动。你可以通过iptables-save 和iptables-restore命令来保存配置。 保存您的防火墙股则到一个文件 # iptables-save >/etc/iptables.up.rules 接着修改 /etc/network/interfaces 脚本自动应用这些规则(末行是添加的) auto eth0 你也可以准备一组规则冰并自动应用它 auto eth0 ◆ 提示 下面的步骤复习了怎样建立你的防火墙规则,并假定它们相对固定(而且对于大多数人来说它们也应该是)。但是假如你要做许多研究工作,你也许想要你的 iptables在你每次重启时保存一次。你可以在 /etc/network/interfaces 里添加像下面的一行: pre-up iptables-restore < /etc/iptables.up.rules post-down iptables-save >/etc/iptables.up.rules 此行将保存规则用于下次启动时使用。 假如你超出了这个指南来编辑iptables,你可能想利用iptables-save和iptables-restore来编辑和测试你的规则。你可以通过使用你喜爱的文本编辑器(此处为gedit)来打开这些规则文件来完成编辑。 # iptables-save >/etc/iptables.test.rules 你会得到一个如下类似的文件(下面是紧接上的例子文件): # Generated by iptables-save v1.3.1 on Sun Apr 23 06:19:53 2006 注意到这些都是减去iptables命令的iptables语句。随意编辑这些命令、完成後保存它们。然後简单的测试下: # iptables-restore < /etc/iptables.test.rules 测试完毕後,假如你还没添加iptables-save命令 到 /etc/network/interfaces 里面,记得不要丢失了你的更改: # iptables-save >/etc/iptables.up.rules ◆ 更详细的日志 # Generated by iptables-save v1.3.1 on Sun Apr 23 05:32:09 2006 请注意 一个名为 LOGNDROP的链在文件顶部。而且,INPUT链底部标准的DROP被替换成了LOGNDROP,同时添加了协议描述so it makes sense looking at the log。最後我们在LOGNDROP链尾部丢弃了这些流量。下面的行告诉我们发生了什么: * --limit 设置记录相同规则到syslog中的次数 ◆ 禁用防火墙 假如您要临时禁用防火墙,您可以通过下面的命令清空所偶的规则: # iptables -F ◆ 轻松配置通过 GUI 新手可以利用 Firetarter(一个gui工具)---仓库中的可用软件(新立德或apt-get 获得)来配置她或他的iptables规则,而需要命令行知识。请查看指南,尽管…… 配置很简单,但是对于高级用户来说可能远远不能满足。然而它对于大多数的家庭用户来说是足够的…… 。(我)建议您使用firestarter在策略表中将出站配置为 “限制”,而将您需要的连接类型(如用于http的80、https的443,msn chat的1683等等)加入白名单。您也可以通过它查看进出您计算机的活动连接…… 。防火墙会一直保持下去一旦通过向导配置完毕。拨号用户必须在向导中指定它在拨号时自动启动。 firestarter主页: http://www.fs-security.com/ (再次, 仓库源中可用, 不需要编译) 指南: http://www.fs-security.com/docs/tutorial.php 个人笔记:不幸运的是,它没有阻断(或询问用户)特定应用/程序的选项……。因此,我的理解是一旦启用了80端口(例如,用于访问网页),那么任何程序都可以通过80端口连接任何服务器、做任何它想做的事……
最新文章

0.1537s , 11744.3359375 kb

Copyright © 2025 Powered by 计数排序真的不重要?,汇智坊  滇ICP备2023006006号-2

sitemap

Top