排序算法(三)—— 桶排序和计数排序

桶排序、计数排序时间复杂度是线性的O(n),因而也叫线性排序,这两个算法是基于非比较的排序算法,不涉及元素之间的比较。

1. 桶排序(Bucket sort)

桶排序的核心思想是把要排序的数据分组放到桶里,然后再把每个桶里的数据单独进行排序,之后把数据取出,组成的序列就是有序的了。主要应用于处理数值分布比较均匀但是数据量很大的场景。相对来说,我们一般使用的排序算法的时间复杂度在nlog(n)到n^2之间,如果n很大时,运算的效率就比较低了,如果我们能把数据分成k组,再单独进行排序,假设原来算法的时间复杂度是O(n^2):

那么拆分到k个桶里之后,时间复杂度就是k*(n/k)^2 = (n^2)/k, 如果k足够大,那么桶排序的时间复杂度就接进O(n).

场景:将超市订单的金额进行桶排序{0,20,10,30,5,22,34,49,46,11,38,6,3}

我们可以分为以下几个桶:

0-9: {0,3,5,6}

10-19:{10,11}

20-29:{20,22}

30-39:{30,34,38}

40-49:{46,49}

 

放入桶里分别排好序之后取出拼接即可得到有序的序列。

 

2. 计数排序(Count sort)

计数排序可以说是桶排序的一种特殊情况。比如对某省高考70万考生总分进行排序。假设总分为800分,那么可能的分数为0-800,此时我们将这801种分数分为801个桶,每个桶内的数值都是相同的,可以节约桶内排序的时间,此时依次扫描每一个桶,将考生的分数一次拷贝到新的数据里既可以了。此时算法的时间复杂度就是遍历数据的复杂度,为O(n)。该算法虽然时间复杂度比较低,但是应用并不广泛,因为条件太苛刻了,只能在数据量较大并且数值分布范围较小的情况下应用。

 

假设我们有序列:a[] = {2,5,3,0,2,3,0,3,3,2,1,5,6},利用计数排序算法从小到大进行排序

首先我们观察到数据的区间范围在0~6之间,那么我们可以建立一个countArr的数值,统计每个数字的大小。

 

countArr的下标及对应值:

下标

0

1

2

3

4

5

6

对应值的个数

2

1

3

4

0

2

1

 

然后,我们再统计小于或者等于下标的值有多少个,即:

下标

0

1

2

3

4

5

6

小于等于对应值的个数

2

3

6

10

10

12

13

 

最后我们将原序列从后往前遍历一遍放入orderArr数组中,从后往前遍历,是为了保证序列的稳定性,往下看实现方法就知道为什么从后往前遍历是稳定的。(所谓稳定性是指序列中有多个相同的数据时,完成排序后,这些相同数据的前后位置保持不变)

首先, a[12] = 6

那么我们将a[12]放入到orderArr数组中,对应的下标是count[6]-1(减一是因为我们统计的是个数,而数组的下标从0开始),然后countArr[6]--。

 

数组orderArr::

下标

0

1

2

3

4

5

6

7

 

8

9

10

11

12

数值

 

 

 

 

 

 

 

 

 

 

 

 

 

6

然后看a[11] = 5:

我们将a[11]放入到orderArr数组中,对应的下标是count[5]-1,然后count[5]--;

下标

0

1

2

3

4

5

6

7

 

8

9

10

11

12

数值

 

 

 

 

 

 

 

 

 

 

 

 

5

6

 

再进行5次以上运算,可以得到:

下标

0

1

2

3

4

5

6

7

 

8

9

10

11

12

数值

0

 

1

 

 

2

 

 

 

3

3

 

5

6

 

我们可以发现,当遍历完整个数组,这个orderArr就会被填充完整,并且已经有序,此时我们只要将数据拷贝到原数组a[]中就可以了。

C语言代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

void countingSort(int a[], int size);

int main()
{
    int i=0;
    int a[] = {2,5,3,0,2,3,0,3,3,2,1,5,6};
    //排序前
    for(i=0; i<sizeof(a)/sizeof(int); i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    //排序
    countingSort(a,sizeof(a)/sizeof(int));

    //排序后
    for(i=0; i<sizeof(a)/sizeof(int); i++)
    {
        printf("%d ", a[i]);
    }
    printf("\n");

    getchar();

    return 0;
}


void countingSort(int a[], int size)
{
    int i = 0;
    int max = 0;
    int *countArr = NULL;//计数数组
    int *orderArr = NULL;//临时数组

    for(i=0; i<size; i++)
    {
        if(a[i] > max)
        {
            max = a[i];
        }
    }

    countArr = (int *)malloc(sizeof(int) * (max+1));
    orderArr = (int *)malloc(sizeof(int) * (size));
    if((NULL == countArr) || (NULL == orderArr))
    {
        return ;
    }
    memset(countArr, 0, sizeof(int) * (max+1));
    memset(orderArr, 0, sizeof(int) * (size));

    //统计每个值的个数
    for(i=0; i<size; i++)
    {
        countArr[a[i]] += 1;
    }

    //统计<=下标值的个数
    for(i=1; i<size; i++)
    {
        countArr[i] += countArr[i-1];
    }


    //为了保证稳定性,从后往前排序
    for(i=size-1; i>=0; i--)
    {
        orderArr[countArr[a[i]]-1] = a[i];
        countArr[a[i]]--;
    }

    //将临时数组的数据拷贝到原数组
    for(i=0; i<size; i++)
    {
        a[i] = orderArr[i];
    }

}

如果有误欢迎指正。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值