八大排序总结(8)——线性时间复杂度的排序(桶排序,基数排序,计数排序)【用空间换时间】(c语言实现)

>>>八大排序总结(1)——冒泡排序(Bubble Sort)(c语言实现)<<<

>>>八大排序总结(2)——选择排序(Selection Sort)(c语言实现)<<<

>>>八大排序总结(3)——插入排序(Insertion Sort)(c语言实现)<<<

>>>八大排序总结(4)——快速排序(Quick Sort)(c语言实现)<<<

>>>八大排序总结(5)——归并排序(Merge Sort)(c语言实现)<<<

>>>八大排序总结(6)——希尔排序(Shell Sort)(c语言实现)<<<

>>> 八大排序总结(7)——堆排序(Bubble Sort)(c语言实现)<<<

>>>八大排序 时间复杂度,空间复杂度,稳定性的比较<<<


目录

1.桶排序(Bucket Sort)

代码+分析

2.基数排序 

代码+分析

3.计数排序 

 代码+分析


1.桶排序(Bucket Sort)

基本思路是:

1. 将待排序元素划分到不同的痛。先扫描一遍序列求出最大值 maxV 和最小值 minV ,设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。

2.对每个桶内的元素进行排序。可以选择任意一种排序算法。

3. 将各个桶中的元素合并成一个大的有序序列。

4.假设数据是均匀分布的,则每个桶的元素平均个数为 n/k 。假设选择用快速排序对每个桶内的元素进行排序,那么每次排序的时间复杂度为 O(n/klog(n/k)) 。总的时间复杂度为 O(n)+O(m)O(n/klog(n/k)) = O(n+nlog(n/k)) = O(n+nlogn-nlogk 。当 k 接近于 n 时,桶排序的时间复杂度就可以金斯认为是 O(n) 的。即桶越多,时间效率就越高,而桶越多,空间就越大。

代码+分析

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

extern void quick_sort(int a[], int p, int q);/* not necessary */
struct barrel {
    int node[10];
    int count;/* the num of node */
};

void bucket_sort(int data[], int size)
{
    int max, min, num, pos;
    int i, j, k;
    struct barrel *pBarrel;

    max = min = data[0];
    for (i = 1; i < size; i++)
    {
        if (data[i] > max)
            max = data[i];
         else if (data[i] < min)
            min = data[i];
    }
    num = (max - min + 1) / 10 + 1;
    pBarrel = (struct barrel*)malloc(sizeof(struct barrel) * num);
    memset(pBarrel, 0, sizeof(struct barrel) * num);

    /* put data[i] into barrel which it belong to */
    for (i = 0; i < size; i++)
    {
        k = (data[i] - min + 1) / 10;/* calculate the index of data[i] in barrel */
        (pBarrel + k)->node[(pBarrel + k)->count] = data[i];
        (pBarrel + k)->count++;
    }

    pos = 0;
    for (i = 0; i < num; i++)
    {
        quick_sort((pBarrel+i)->node, 0, (pBarrel+i)->count);/* sort node in every barrel */
        for (j = 0; j < (pBarrel+i)->count; j++)
        {
            data[pos++] = (pBarrel+i)->node[j];
        }
    }
    free(pBarrel);
}

2.基数排序 

这是一种非比较排序算法,时间复杂度是 O(n) 。它的主要思路是,
1. 将所有待排序整数(注意,必须是非负整数)统一为位数相同的整数,位数较少的前面补零。一般用10进制,也可以用16进制甚至2进制。所以前提是能够找到最大值,得到最长的位数,设 k 进制下最长为位数为 d 。
2. 从最低位开始,依次进行一次稳定排序。这样从最低位一直到最高位排序完成以后,整个序列就变成了一个有序序列。
举个例子,有一个整数序列,0, 123, 45, 386, 106,下面是排序过程:

1.第一次排序,个位,000 123 045 386 106,无任何变化
2.第二次排序,十位,000 106 123 045 386
3.第三次排序,百位,000 045 106 123 386
4.最终结果,0, 45, 106, 123, 386, 排序完成。

代码+分析

#include<stdio.h>
#include <stdlib.h>
#define Max_ 10      //数组个数
#define RADIX_10 10    //整形排序
#define KEYNUM_31 31     //关键字个数,这里为整形位数
// 打印结果
void Show(int  arr[], int n)
{
    int i;
    for ( i=0; i<n; i++ )
        printf("%d  ", arr[i]);
    printf("\n");
}

// 找到num的从低到高的第pos位的数据
int GetNumInPos(int num,int pos)
{
    int temp = 1;
    for (int i = 0; i < pos - 1; i++)
    temp *= 10;

    return (num / temp) % 10;
}


//基数排序  pDataArray 无序数组;iDataNum为无序数据个数
void RadixSort(int* pDataArray, int iDataNum)
{
    int *radixArrays[RADIX_10];    //分别为0~9的序列空间
    for (int i = 0; i < 10; i++)
    {
        radixArrays[i] = (int *)malloc(sizeof(int) * (iDataNum + 1));
        radixArrays[i][0] = 0;    //index为0处记录这组数据的个数
    }

    for (int pos = 1; pos <= KEYNUM_31; pos++)    //从个位开始到31位
    {
        for (int i = 0; i < iDataNum; i++)    //分配过程
        {
            int num = GetNumInPos(pDataArray[i], pos);
            int index = ++radixArrays[num][0];
            radixArrays[num][index] = pDataArray[i];
        }

        for (int i = 0, j =0; i < RADIX_10; i++)    //收集
        {
            for (int k = 1; k <= radixArrays[i][0]; k++)
                pDataArray[j++] = radixArrays[i][k];
                radixArrays[i][0] = 0;    //复位
        }
    }
}

3.计数排序 

是一种O(n)的排序算法,其思路是开一个长度为 maxValue-minValue+1 的数组,然后

  • 分配。扫描一遍原始数组,以当前值- minValue 作为下标,将该下标的计数器增1。
  • 收集。扫描一遍计数器数组,按顺序把值收集起来。

举个例子, nums=[2, 1, 3, 1, 5] , 首先扫描一遍获取最小值和最大值, maxValue=5 , minValue=1 ,于是开一个长度为5的计数器数组 counter ,
1. 分配。统计每个元素出现的频率,得到 counter=[2, 1, 1, 0, 1] ,例如 counter[0] 表示值 0+minValue=1 出现了2次。
2. 收集。 counter[0]=2 表示 1 出现了两次,那就向原始数组写入两个1, counter[1]=1 表示 2 出现了1次,那就向原始数组写入一个2,依次类推,最终原始数组变为 [1,1,2,3,5] ,排序好了。

计数排序本质上是一种特殊的桶排序,当桶的个数最大的时候,就是计数排序。

 代码+分析

//计数排序   参数:数组及其长度
void BucketSort(int* arr , int len)
{
    int tmpArrLen = GetMaxVal(arr , len) + 1;
    int tmpArr[tmpArrLen];  //获得空桶大小
    int i, j;

    for( i = 0; i < tmpArrLen; i++)  //空桶初始化
        tmpArr[i] = 0;

    for(i = 0; i < len; i++)
        tmpArr[ arr[i] ]++;

    for(i = 0, j = 0; i < tmpArrLen; i ++)
    {
        while( tmpArr[ i ] != 0) //对每个不是空的桶子进行排序。
        {
            arr[j ] = i;  //从不是空的桶子里把项目再放回原来的序列中。
            j++;
            tmpArr[i]--;
        }
    }
}

int GetMaxVal(int* arr, int len)//遍历数组取最大值
{
    int maxVal = arr[0]; //假设最大为arr[0]

    for(int i = 1; i < len; i++)  //遍历比较,找到大的就赋值给maxVal
    {
        if(arr[i] > maxVal)
            maxVal = arr[i];
    }

    return maxVal;  //返回最大值
}

  • 为什么同一数位的排序子程序要用稳定排序?因为稳定排序能将上一次排序的成果保留下来。例如十位数的排序过程能保留个位数的排序成果,百位数的排序过程能保留十位数的排序成果。能不能用2进制?能,可以把待排序序列中的每个整数都看成是01组成的二进制数值。那这样的话,岂不是任意一个非负整数序列都可以用基数排序算法?理论上是的,假设待排序序列中最大整数为2 4 . 1,则最大位数 d=64 ,时间复杂度为 O(64n) 。可见任意一个非负整数序列都可以在线性时间内完成排序。
  • 既然任意一个非负整数序列都可以在线性时间内完成排序,那么基于比较排序的算法有什么意义呢?基于比较的排序算法,时间复杂度是 O(nlogn) ,看起来比 O(64n) 慢,仔细一想,其实不是, O(nlogn) 只有当序列非常长,达到2 个元素的时候,才会与 O(64n) 相等,因此,64这个常数系数太大了,大部分时候, n 远远小于2 ,基于比较的排序算法还是比 O(64n) 快的。
  • 当使用2进制时, k=2 最小,位数 d 最大,时间复杂度 O(nd) 会变大,空间复杂度 O(n+k) 会变小。当用最大值作为基数时, k=maxV 最大, d=1 最小,此时时间复杂度 O(nd) 变小,但是空间复杂度 O(n+k) 会急剧增大,此时基数排序退化成了计数排序。
算法时间复杂度空间复杂度适用场景
桶排序O(nd)_O(n+k)1.非负整数.2.maxV和minV尽可能小
基数排序O(n+k)O(n+k)元素尽可能均匀分布
计数排序O(n+maxV-minV)O(maxV-minV)maxV和minV差距尽可能小

其中, d 表示位数, k 在基数排序中表示 k 进制,在桶排序中表示桶的个数, maxV 和 minV 表示元
素最大值和最小值。

  • 首先,基数排序和计数排序都可以看作是桶排序。
  • 计数排序本质上是一种特殊的桶排序,当桶的个数取最大( maxV-minV+1 )的时候,就变成了计数排序。
  • 基数排序也是一种桶排序。桶排序是按值区间划分桶,基数排序是按数位来划分;基数排序可以看做是多轮桶排序,每个数位上都进行一轮桶排序。
  • 当用最大值作为基数时,基数排序就退化成了计数排序。
  • 当使用2进制时, k=2 最小,位数 d 最大,时间复杂度 O(nd) 会变大,空间复杂度 O(n+k) 会变小。当用最大值作为基数时, k=maxV 最大, d=1 最小,此时时间复杂度 O(nd) 变小,但是空间复杂度 O(n+k) 会急剧增大,此时基数排序退化成了计数排序。、

欢迎大家评论指正,谢谢◕‿◕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值