计数排序 (算法导论 p98)
2011-12-05 17:27
218 查看
计数排序是一种算法复杂度O(n)的排序方法,适合于小范围集合的排序。比如100万学生参加高考,我们想对这100万学生的数学成绩(假设分数为0到100)做个排序。我们如何设计一个最高效的排序算法。本文不光给出计数排序算法的传统写法,还将一步步深入讨论算法的优化,直到时间复杂度和空间复杂度最优。
先看看计数排序的定义
Countingsort(sometimesreferredtoas
ultrasortormathsort
A).ItusesthisrangetocreateanarrayCofthislength.Eachindex
iinarrayCisthenusedtocounthowmanyelementsinAhavethevalue
i;thencountsstoredinCcanthenbeusedtoputtheelementsin
Aintotheirrightpositionintheresultingsortedarray.Thealgorithmwascreatedby
计数排序是一个类似于桶排序的排序算法,其优势是对已知数量范围的数组进行排序。它创建一个长度为这个数据范围的数组C,C中每个元素记录要排序数组中对应记录的出现个数。这个算法于1954年由HaroldH.Seward提出。
下面以示例来说明这个算法
假设要排序的数组为A={1,0,3,1,0,1,1}
这里最大值为3,最小值为0,那么我们创建一个数组C,长度为4.
然后一趟扫描数组A,得到A中各个元素的总数,并保持到数组C的对应单元中。
比如0的出现次数为2次,则C[0]=2;1的出现次数为4次,则C[1]=4
由于C是以A的元素为下标的,所以这样一做,A中的元素在C中自然就成为有序的了,这里我们可以知道顺序为0,1,3(2的计数为0)
然后我们把这个在C中的记录按每个元素的计数展开到输出数组B中,排序就完成了。
也就是B[0]到B[1]为0B[2]到B[5]为1这样依此类推。
这种排序算法,依靠一个辅助数组来实现,不基于比较,算法复杂度为O(n),但由于要一个辅助数组C,所以空间复杂度要大一些,由于计算机的内存有限,这种算法不适合范围很大的数的排序。
注:基于比较的排序算法的最佳平均时间复杂度为O(nlogn)
Countingsort
Dependsonakeyassumption:numberstobesortedareintegersin{0,1,...,k}.
Input:A[1..n],whereA[j]∈{0,1,...,k}forj=1,2,...,n.ArrayAand
valuesnandkaregivenasparameters.
Output:B[1..n],sorted.Bisassumedtobealreadyallocatedandisgivenasa
parameter.
Auxiliarystorage:C[0..k]
8-4LectureNotesforChapter8:SortinginLinearTime
COUNTING-SORT(A,B,n,k)
fori←0tok
doC[i]←0
forj←1ton
doC[A[j]]←C[A[j]]+1
fori←1tok
doC[i]←C[i]+C[i−1]
forj←ndownto1
doB[C[A[j]]]←A[j]
C[A[j]]←C[A[j]]−1
DoanexampleforA=21,51,31,01,22,32,02,33
Countingsortisstable(keyswithsamevalueappearinsameorderinoutputas
theydidininput)becauseofhowthelastloopworks.
上面这段引自麻省理工大学计算机算法教材的技术排序部分,我不做翻译了。这个就是这个算法的典型解法,我把它作为方案1.
这个算法的实际扫描次数为n+k(不包括写的次数)
方案1
publicstaticvoidSort(int[]A,outint[]B,intk)
{
Debug.Assert(k>0);
Debug.Assert(A!=null);
int[]C=newint[k+1];
B=newint[A.Length];
for(intj=0;j<A.Length;j++)
{
C[A[j]]++;
}
for(inti=1;i<=k;i++)
{
C[i]+=C[i-1];
}
for(intj=A.Length-1;j>=0;j--)
{
B[C[A[j]]-1]=A[j];
C[A[j]]--;
}
}
上面代码是方案1的解法,也是计数排序算法的经典解法,麻省的教材上也是这样解。不过这个解法并不是最优的,因为空间复杂度还应该可以优化,我们完全可以不要那个输出的数组B,直接对A进行排序。在继续看方案2之前,我建议大家先自己思考一下,看看是否有办法省略掉数组B
方案2
我们对上述代码进行优化publicstaticvoidSort(int[]A,intk){Debug.Assert(k>0);Debug.Assert(A!=null);int[]C=newint[k+1];for(intj=0;j<A.Length;j++){C[A[j]]++;}intz=0;for(inti=0;i<=k;i++){while(C[i]-->0){A[z++]=i;}}}
由于C数组下标i就是A的值,所以我们不需要保留A中原来的数了,这个代码减少了一个数组B,而且要比原来的代码简化了很多。
和快速排序的速度比较
拿本文刚开始那个高考成绩的例子来做int[]A=newint[1000000];
int[]B=newint[1000000];
Randomrand=newRandom();
for(inti=0;i<A.Length;i++)
{
A[i]=rand.Next(0,100);
}
A.CopyTo(B,0);
Stopwatchsw=newStopwatch();
sw.Start();
Array.Sort(B);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
CountingSort.Sort(A,100);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
输出结果
134//快速排序
18//计数排序
可见计数排序要比快速排序快将近6倍左右。
转自
-----------------------------------------------------------------------------------------------------------------------------------------------------------
//具体实现1
#include<stdio.h>
#defineLENGTHA6
#defineLIMIT10
voidCOUNTING_SORT(int*A,intn,int*B,intk);
intmain()
{
inti,A[LENGTHA]={0,3,2,5,8,4},B[LENGTHA]={0};
COUNTING_SORT(A,LENGTHA,B,LIMIT);
for(i=1;i<LENGTHA;i++)
printf("%d,",B[i]);
return0;
}
voidCOUNTING_SORT(int*A,intn,int*B,intk)
{
inti,j,*C;
C=(int*)malloc(4*k+4);
for(i=0;i<k;i++)//fori<-0tok
C[i]=0;//doC[i]<-0
for(j=1;j<n;j++)//forj<-1tolength[A],令n=lengthA
C[A[j]]++;//doC[A[j]]<-C[A[j]]+1
//C[i]包含等于i的元素个数
for(i=1;i<k;i++)//fori<-1tok
C[i]+=C[i-1];//doC[i]<-+C[i-1]
//C[i]包含小于或等于i的元素个数
for(j=n-1;j>=1;j--)//forj<-length[A]downto1
{
B[C[A[j]]]=A[j];//doB[C[A[j]]]<-A[j]
C[A[j]]--;//C[A[j]]<-C[A[j]]-1
}
free(C);
}
//具体实现2
#include<stdio.h>
inta[100],b[100];
voidcounting_sort(inta[],intb[],intk,intl)
{
inti,j;
intc[100]={0};
for(j=0;j<l;j++)
c[a[j]]++;
for(i=1;i<=k;i++)
c[i]=c[i]+c[i-1];
for(j=l-1;j>=0;j--)
{
b[c[a[j]]-1]=a[j];
c[a[j]]--;
}
for(i=0;i<l;i++)
printf("%d",b[i]);
printf("\n");
}
/*改良版,不需要使用b数组
voidcounting_sort(inta[],intk,intl)
{
intj;
intc[100]={0};
for(j=0;j<l;j++)
c[a[j]]++;
intz=0;
for(j=0;j<=k;j++)
while(c[i]-->0)
a[z++]=i;
由于C数组下标i就是A的值,所以我们不需要保留A中原来的数了,
这个代码减少了一个数组B,而且要比原来的代码简化了很多。
}
*/
intmain()
{
inti=0;
/*while(scanf("%d",&a[i])!=EOF)
{
i++;
}
*/
for(i=0;i<8;i++)
scanf("%d",&a[i]);
counting_sort(a,b,6,i);
}
相关文章推荐
- 线性时间排序之计数排序(算法导论8.2)
- 插入排序,合并排序,堆排序,快速排序,计数排序的实现(算法导论)
- [算法导论]计数排序
- 算法导论-8.1计数排序
- 算法导论例程——计数排序
- 算法导论8.2-4习题解答(计数排序)
- 【算法导论】第八章之计数排序
- 【算法导论】第8章线性时间排序_计数排序、基数排序、桶排序
- 算法导论学习笔记(六):计数排序与基数排序
- 【算法导论】计数排序
- 计数排序(线性时间排序)--【算法导论】
- 算法导论8.3基数排序底层计数排序
- 【算法导论】计数排序
- 【算法导论】第五课 线性排序(基数排序 计数排序)
- 【算法导论】计数排序
- 【算法导论之四】计数排序
- 计数排序----算法导论
- 算法导论之排序:快速排序、归并排序、计数排序、基数排序、桶排序