计数排序、桶排序和基数排序

计数排序

计数排序是一种算法复杂度 O(n) 的排序方法,适合于小范围集合的排序。比如100万学生参加高考,我们想对这100万学生的数学成绩(假设分数为0到100)做个排序。

计数排序是一个类似于桶排序的排序算法,其优势是对已知数量范围的数组进行排序。它创建一个长度为这个数据范围的数组C,C中每个元素记录要排序数组中对应记录的出现个数。这个算法于1954年由 Harold H. Seward 提出。

这种排序算法,依靠一个辅助数组来实现,不基于比较,算法复杂度为 O(n) ,但由于要一个辅助数组C,所以空间复杂度要大一些,由于计算机的内存有限,这种算法不适合范围很大的数的排序。

例子:

假设要排序的数组为 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

 

image

由于C 是以A的元素为下标的,所以这样一做,A中的元素在C中自然就成为有序的了,这里我们可以知道 顺序为 0,1,3 (2 的计数为0)

然后我们把这个在C中的记录按每个元素的计数展开到输出数组B中,排序就完成了。

也就是 B[0] 到 B[1] 为0  B[2] 到 B[5] 为1 这样依此类推。


method1:

    public static void Sort(int[] A, out int[] B, int k)
        {
            Debug.Assert(k > 0);
            Debug.Assert(A != null);
 
            int[] C = new int[k + 1];
            B = new int[A.Length];
 
            for (int j = 0; j < A.Length; j++)
            {
                C[A[j]]++;
            }
 
            for (int i = 1; i <= k; i++)
            {
                C[i] += C[i-1];
            }
 
            for (int j = A.Length - 1; j >= 0; j--)
            {
                B[C[A[j]]-1] = A[j];
                C[A[j]]--;
            }
 
        }

method2

  public static void Sort(int[] A, int k)
        {
            Debug.Assert(k > 0);
            Debug.Assert(A != null);            int[] C = new int[k + 1];            for (int j = 0; j < A.Length; j++)
            {
                C[A[j]]++;
            }            int z = 0;            for (int i = 0; i <= k; i++)
            {                while (C[i]-- > 0)
                {
                    A[z++] = i;
                }
            }
        }


参考:http://www.cnblogs.com/eaglet/archive/2010/09/16/1828016.html


桶排序

桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。

(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:

O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)

 例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。   

总结:桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。桶式排序不再是基于比较的了,它和基数排序同属于分配类的排序,这类排序的特点是事先要知道待排序列的一些特征。

基数排序

第一步

以LSD为例,假设原来有一串数值如下所示:

73, 22, 93, 43, 55, 14, 28, 65, 39, 81

首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:

0

1 81

2 22

3 73 93 43

4 14

5 55 65

6

7

8 28

9 39


第二步

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

81, 22, 73, 93, 43, 14, 55, 65, 28, 39

接着再进行一次分配,这次是根据十位数来分配:

0

1 14

2 22 28

3 39

4 43

5 55

6 65

7 73

8 81

9 93


第三步

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

14, 22, 28, 39, 43, 55, 65, 73, 81, 93

这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

#include<iostream>
using namespace std;
int a[]= {1,255,8,6,25,47,14,35,58,75,96,158,657};
const int len=sizeof(a)/sizeof(int);
int b[9][len]= {0}; //将b全部置0
//桶排序函数
void bucketSort(int a[]);
void distributeElments(int a[],int b[9][len],int digits);
void collectElments(int a[], int b[9][len]);
//返回最大值的位数
int numOfDigits(int a[]);
void zeroBucket(int b[9][len]); //将b数组中的全部元素置0
int main()
{
    cout<<"原始数组:";
    for(int i=0; i<len; i++)
        cout<<a[i]<<",";
    cout<<endl;
    bucketSort(a);
    cout<<"排序后数组:";
    for(int i=0; i<len; i++)
        cout<<a[i]<<",";
    cout<<endl;
    return 0;
}
void bucketSort(int a[])
{
    int digits=numOfDigits(a);
    for(int i=1; i<=digits; i++)
    {
        distributeElments(a,b,i);
        collectElments(a,b);
        if(i!=digits)
            zeroBucket(b);
    }
}
int numOfDigits(int a[])
{
    int largest=0;
    for(int i=0; i<len; i++) //获取最大值
        if(a[i]>largest)
            largest=a[i];
    int digits=0; //digits为最大值的位数
    while(largest)
    {
        digits++;
        largest/=10;
    }
    return digits;
}

void distributeElments(int a[],int b[9][len],int digits)
{
    int divisor=10; //除数
    for(int i=1; i<digits; i++)
        divisor*=10;
    for(int j=0; j<len; j++)
    {
        int numOfDigist=(a[j]%divisor-a[j]%(divisor/10))/(divisor/10);
        //numOfDigits为相应的(divisor/10)位的值,如当divisor=10时,求的是个位数
        int num = ++b[numOfDigist][0];//用b中第一列的元素来储存每行中元素的个数
        b[numOfDigist][num]=a[j];
    }
}
void collectElments(int a[], int b[9][len])
{
    int k=0;
    for(int i=0; i<=9; i++)
        for(int j=1; j<=b[i][0]; j++){
                cout<<"b["<<i<<","<<j<<"]="<<b[i][j]<<endl;
            a[k++]=b[i][j];
        }

}
void zeroBucket(int b[][len])
{
    for(int i=0; i<9; i++)
        for(int j=0; j<len; j++)
            b[i][j]=0;
}

个位排序

b[1,1]=1

b[4,1]=14

b[5,1]=255

b[5,2]=25

b[5,3]=35

b[5,4]=75

b[6,1]=6

b[6,2]=96

b[7,1]=47

b[7,2]=657

b[8,1]=8

b[8,2]=58

b[8,3]=158

十位排序

b[0,1]=1

b[0,2]=6

b[0,3]=8

b[1,1]=14

b[2,1]=25

b[3,1]=35

b[4,1]=47

b[5,1]=255

b[5,2]=657

b[5,3]=58

b[5,4]=158

b[7,1]=75

b[9,1]=96

百位排序

b[0,1]=1

b[0,2]=6

b[0,3]=8

b[0,4]=14

b[0,5]=25

b[0,6]=35

b[0,7]=47

b[0,8]=58

b[0,9]=75

b[0,10]=96

b[1,1]=158

b[2,1]=255

b[6,1]=657

扩展:http://www.cnblogs.com/Braveliu/archive/2013/01/21/2870201.html

LSD的排序方式由数值的最右边(低位)开始,而MSD则相反,由数值的最左边(高位)开始。

注意一点:LSD的基数排序适用于位数少的数列,如果位数多的话,使用MSD的效率会比较好。

MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中


转载于:https://my.oschina.net/hnuweiwei/blog/293056

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值