计数排序法是一种简单的排序方法,这种排序算法对一个待排序的数组进行排序,并将排序结果放到另一个新的数组中。计数排序算法针对待排序数组中的每个记录,扫描待排序的数组一趟,统计待排序数组中有多少个记录的值比该记录的值小。假设针对某一个记录,统计出的计数值为c,那么,这个记录在新的有序数组中的合适的存放位置即为c。
假设有三个数组:
数组 A:待排序数组(非空,数据个数n)
数组 B:排序后的数组
数组 count:纪录A中某个数据在表B中的位置,初始值为0
对于A中某个数据Xi(0<=i<n),与表中各个数据Xj(0<=i<n)进行比较,记录下比Xi小的值的个数到count[i]。但是,我们发现,数据与自身的比较是多余的,而且当Xi与Xj比较后,就没有必要再比较Xj和Xi了。在此基础上我们对之前的操作方式进行一些变化。我们对A中的数据Xi与Xj进行比较,所不同的是Xj的j的范围为 i<j<=n ,即每个Xi只和其后的数据进行比较。在比较过程中,如果Xi > Xj ,则count[i] = count[i]+1;否则,count[j]=count[j]+1 。当算法完毕的时候,count数组中就记录了A中的各个数据在B中的实际位置。算法过程如下所示:
n 0 1 2 3 /*数组的索引位置*/
A 21 32 17 21 /*数组中的数据*/
C [0] [0] [0] [0] /*count的初始值*/
算法开始i=0,j取值{1 ,2,3}。
(1)X0<X1 ,所以count[1]+1
n 0 1 2 3 /*数组的索引位置*/
A 21 32 17 21 /*数组中的数据*/
C [0] [1] [0] [0] /*count的值*/
(2)X0>X2 ,所以count[0]+1
n 0 1 2 3 /*数组的索引位置*/
A 21 32 17 21 /*数组中的数据*/
C [1] [1] [0] [0] /*count的值*/
(3)X0=X3 ,所以count[3]+1
n 0 1 2 3 /*数组的索引位置*/
A 21 32 17 21 /*数组中的数据*/
C [1] [1] [0] [1] /*count的值*/
接下来i=1 ,j取值{2,3}
…….
如此继续直至算法完毕,则
n 0 1 2 3 /*数组的索引位置*/
A 21 32 17 21 /*数组中的数据*/
C [1] [3] [0] [2] /*count的初始值*/
接下来的要做的只是按count中的值将A中的数据放到B中相应的位置上了,即 B[count[i]] = A[i],则数组B中数据为
B [17] [21] [21] [32]
至此计数排序算法完成。
在上述过程中,我们看到,对于待排序数组中的相同数据,在排序后其先后顺序保持不变,也就是说以上原理的排序算法是一个稳定的排序算法。
算法复杂度:
上述计数排序算法包含两重循环,假设数组A中的数据个数为n,那么在程序执行过程中,第二层循环控制变量j的执行次数随着第一层循环控制变量i的变化依次为n-1 , n-2,n-3,…….,0 ,这是一个差值为1的n项等差数列,所以第二层循环的执行次数为(n-1+0)*n/2,即n*(n-1)/2,所以该算法的时间复杂度为O(n2)。
在计数排序算法的代码中,我们假定输入是个数组A[0...n-1],length[A]=n。另外还需要两个数组:存放排序结果的B[0...n-1],以及提供临时存储区的C[0...k].
#include
<
stdio.h
>
#include
<
stdlib.h
>
#include
<
iostream
>
using
namespace
std;
void
CountSort(
int
a[],
int
b[],
int
k,
int
num)
{
int
*
c
=
new
int
[k
+
1
];
for
(
int
i
=
0
;i
<=
k;i
++
)
c[i]
=
0
;
int
size
=
num;
for
(
int
j
=
0
;j
<
size;j
++
)
c[a[j]]
++
;
//
c[i]包含等于i的元素个数
for
(i
=
1
;i
<=
k;i
++
)
c[i]
=
c[i]
+
c[i
-
1
];
//
c[i]包含小于等于i的元素个数
for
(j
=
size
-
1
;j
>=
0
;j
--
)
{
b[c[a[j]]
-
1
]
=
a[j];
c[a[j]]
--
;
}
delete [] c;
}
void
main()
{
int
num,max;
cout
<<
"
输入最大整数及输入个数
"
<<
endl;
cin
>>
max;
cin
>>
num;
int
*
a
=
new
int
[num];
int
*
b
=
new
int
[num];
cout
<<
"
排序前:
"
<<
endl;
for
(
int
i
=
0
;i
<
num;i
++
)
{
cin
>>
a[i];
if
(a[i]
>
max)
i
--
;
}
CountSort(a,b,max,num);
cout
<<
"
排序后:
"
<<
endl;
for
(
int
j
=
0
;j
<
num;j
++
)
{
cout
<<
b[j]
<<
endl;
}
delete [] a;
delete [] b;
}
一个例子 form:http://zhedahht.blog.163.com/blog/static/25411174201131184017844/
题目:某公司有几万名员工,请完成一个时间复杂度为
O(n)
的算法对该公司员工的年龄作排序,可使用
O(1)
的辅助空间。
分析:排序是面试时经常被提及的一类题目,我们也熟悉其中很多种算法,诸如插入排序、归并排序、冒泡排序,快速排序等等。这些排序的算法,要么是
O(n2
)
的,要么是
O(nlog
n)
的。可是这道题竟然要求是
O(n)
的,这里面到底有什么玄机呢?
题目特别强调是对一个公司的员工的年龄作排序。员工的数目虽然有几万人,但这几万员工的年龄却只有几十种可能。上班早的人一般也要等到将近二十岁才上班,一般人再晚到了六七十岁也不得不退休。
由于年龄总共只有几十种可能,我们可以很方便地统计出每一个年龄里有多少名员工。举个简单的例子,假设总共有
5
个员工,他们的年龄分别是
25
、
24
、
26
、
24
、
25
。我们统计出他们的年龄,
24
岁的有两个,
25
岁的也有两个,
26
岁的一个。那么我们根据年龄排序的结果就是:
24
、
24
、
25
、
25
、
26
,即在表示年龄的数组里写出两个
24
、两个
25
和一个
26
。
想明白了这种思路,我们就可以写出如下代码:
void
SortAges(int
ages[], int
length)
{
if
(ages ==
NULL || length <= 0)
return
;
const
int
oldestAge = 99;
int
timesOfAge[oldestAge + 1];
for
(int
i = 0; i <= oldestAge; ++ i)
timesOfAge[i] = 0;
for
(int
i = 0; i < length; ++ i)
{
int
age = ages[i];
if
(age
< 0 || age > oldestAge)
throw
new
std::exception("age out of
range."
);
++ timesOfAge[age];
}
int
index
= 0;
for
(int
i = 0; i <= oldestAge; ++ i)
{
for
(int
j = 0; j
< timesOfAge[i]; ++ j)
{
ages[index] = i;
++ index;
}
}
}
在上面的代码中,允许的范围是
0
到
99
岁。数组
timesOfAge
用来统计每个年龄出现的次数。某个年龄出现了多少次,就在数组
ages
里设置几次该年龄。这样就相当于给数组
ages
排序了。该方法用长度
100
的整数数组辅助空间换来了
O(n)
的时间效率。由于不管对多少人的年龄作排序,辅助数组的长度是固定的
100
个整数,因此它的空间复杂度是个常数,即
O(1)
。
分享到:
相关推荐
Epp的《离散数学及其应用》是一本广泛使用的教材,尤其在计算机科学、信息科技和数学专业中,为学生提供了深入理解离散结构的基石。 这本书详细介绍了离散数学中的核心概念,包括集合论、逻辑、图论、组合数学、...
《离散数学及其应用》第七版是一本广泛使用的教材,深入浅出地讲解了离散数学的核心概念。这本书包括了集合论、图论、逻辑、关系、函数、组合计数、格和布尔代数等多个主题。 1. 集合论:集合是最基本的离散数学...
根据提供的信息,“离散数学及其应用中文第四版”是一本重要的参考书籍,旨在帮助读者入门并深入了解离散数学的相关概念和技术。离散数学是计算机科学领域中的基础学科之一,它研究的是可数对象的性质与关系,对于...
"高级离散数学及其应用第五版"深入探讨了这一领域的高级概念和应用,对于深化对计算机科学的理解至关重要。本书涵盖了广泛的主题,旨在将离散数学的理论与实际计算机科学问题相结合。 在离散数学中,基础概念包括...
《离散数学及其应用》是由Kenneth H. Rosen编著的,这是一本广泛使用的教材,尤其在计算机科学教育领域。英文第七版延续了前几版的高质量和深入浅出的讲解风格,为学习者提供了丰富的离散数学知识。 这本书涵盖了...
### 单词计数与排序程序分析 #### 一、程序概述 本程序的主要功能是对一组字符进行统计,找出其中出现次数最多的字母及其出现次数,并按照出现次数进行降序排列。程序采用Java语言编写,利用了`HashMap`来存储每个...
"离散数学及其应用"是一本广泛使用的教材,旨在为学生提供在信息时代理解和解决复杂问题所需的数学工具。第四版由Kenneth H. Rosen编著,深入浅出地介绍了离散数学的核心概念,特别适合计算机科学、信息技术及相关...
根据给定文件的信息,我们可以总结出以下几种排序算法的相关...以上介绍了三种排序算法的关键概念及其C++实现方式。这些算法在不同的应用场景下有着各自的优缺点,理解它们的工作原理对于选择合适的排序方法非常重要。
在这个PPT学习教案中,我们将深入探讨生成树的计数以及其在特定问题中的应用。 首先,最小生成树(Minimum Spanning Tree, MST)和最大生成树(Maximum Spanning Tree, MST)是两种常见的生成树类型。最小生成树是...
排序算法有很多种,如冒泡排序、插入排序、选择排序、快速排序、归并排序、堆排序、希尔排序、计数排序、桶排序、基数排序等。每种排序算法都有其独特的优缺点,适用的场景也不同。例如: 1. 冒泡排序:简单直观,...
### 离散数学及其应用 #### 一、离散数学概述 离散数学作为现代数学的一个重要分支,主要研究可以取值为离散而非连续的数学结构与问题。它在计算机科学领域扮演着极其重要的角色,是算法设计、数据结构、数据库...
根据提供的信息,《离散数学及其应用》(中文版,第四版)是由K.H. Rosen编写的,这是一本被广泛采用的国外经典教材。离散数学是计算机科学的基础之一,对于理解和开发现代计算技术至关重要。下面将根据标题、描述及...
在C++实现中,确保稳定性的关键是计数排序的正确应用,因为计数排序也必须是稳定的。 八、性能分析 基数排序的时间复杂度为O(kn),其中k是最大数字的位数,n是待排序元素的数量。由于基数排序不需要比较操作,因此...
基数排序适用于数值范围较小且位数确定的情况,计数排序适用于数值范围较小且连续的情况,桶排序则将数据分配到多个桶中再进行排序,桶越多效率越高,但空间需求也越大。 在选择排序算法时,应考虑以下因素: - ...
### 离散数学及其应用(原书第7版) #### 重要知识点概览 《离散数学及其应用》是离散数学领域内的一本权威教材,由Kenneth H. Rosen编写,本书第七版继续深化了对离散数学理论与实践的理解。离散数学作为一门研究...
计数排序是一种非基于比较的排序算法,适用于整数排序,通过统计每个元素出现的次数,确定每个元素在输出数组中的位置。时间复杂度为O(n+k),其中k为整数范围。 8. 桶排序(Bucket Sort) 桶排序将元素分配到有限...
根据提供的信息,《离散数学及其应用》(中文版,第四版)是一本由K.H. Rosen撰写的关于离散数学的经典教材。这本书被许多学校指定为博士考试的参考书籍之一,可见其在学术领域的权威性和实用性。下面将详细介绍离散...
- **非比较性排序**:这一类排序算法不需要通过元素之间的比较来决定它们的顺序,而是利用数据本身的特性进行排序,例如计数排序、桶排序等。 此外,根据排序后相同元素的相对位置是否保持不变,还可以将排序算法...
本文将探讨线性排序算法,包括桶排序、计数排序和基数排序,并通过一个具体的案例——根据年龄对100万用户数据进行排序,来深入理解这些算法的工作原理及其适用场景。 #### 二、线性排序算法介绍 线性排序算法是一...