您的位置:首页 > 其它

计数排序 (算法导论 p98)

2011-12-05 17:27 218 查看


计数排序是一种算法复杂度O(n)的排序方法,适合于小范围集合的排序。比如100万学生参加高考,我们想对这100万学生的数学成绩(假设分数为0到100)做个排序。我们如何设计一个最高效的排序算法。本文不光给出计数排序算法的传统写法,还将一步步深入讨论算法的优化,直到时间复杂度和空间复杂度最优。

先看看计数排序的定义

Countingsort(sometimesreferredtoas
ultrasortormathsort[1])isa
sortingalgorithmwhich(like
bucketsort)takesadvantageofknowingthe
rangeofthenumbersinthe
arraytobesorted(array
A).ItusesthisrangetocreateanarrayCofthislength.Eachindex
iinarrayCisthenusedtocounthowmanyelementsinAhavethevalue
i;thencountsstoredinCcanthenbeusedtoputtheelementsin
Aintotheirrightpositionintheresultingsortedarray.Thealgorithmwascreatedby
HaroldH.Sewardin1954.

计数排序是一个类似于桶排序的排序算法,其优势是对已知数量范围的数组进行排序。它创建一个长度为这个数据范围的数组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倍左右。

转自/article/4689843.html

-----------------------------------------------------------------------------------------------------------------------------------------------------------

//具体实现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);

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: