数据结构与算法学习笔记13:桶排序+基数排序

数据结构与算法学习笔记13:桶排序+基数排序

桶排序(BucketSort)

  • 在面试的海量数据处理题目中,如对每天数以亿计的数据进行排序,直接排序是一件很恐怖的事情,内存也无法容纳如此多的数据,这时桶排序就可以有效地降低数据的数量级,再对降低了数量级的数据进行排序,可以得到比较良好的效果。

针对于位数相同的小数的排序,将数据分成多个组,各组之内各自排序。利用的是哈希的思想。

平均时间复杂度: O ( N + C ) O(N+C) O(N+C)
最佳时间复杂度: O ( N ) O(N) O(N)
最差时间复杂度: O ( n 2 ) O(n ^ 2) O(n2)
空间复杂度: O ( N + M ) O(N + M) O(N+M)

对于待排序序列大小为 N,共分为 M 个桶,主要步骤有:N 次循环,将每个元素装入对应的桶中 O ( N ) O(N) O(N);M 次循环,对每个桶中的数据进行排序(平均每个桶有 N M \frac{N}{M} MN 个元素),一般使用较为快速的排序算法,快速的排序算法时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN),因此每个桶的时间复杂度为 ∑ O ( N M ∗ l o g N M ) ∑ O(\frac{N}{M}*log\frac{N}{M}) O(MNlogMN),总的来说,整个桶排序的时间复杂度为: O ( N + C ) , C = N ∗ ( l o g N − l o g M ) O(N + C),C=N*(logN-logM) O(N+C)C=N(logNlogM),推导过程根据上述步骤完成如下:
O ( N ) + O ( M ∗ ( N M ∗ l o g ( N M ) ) ) = O ( N ) + O ( N ∗ ( l o g ( N M ) ) = O ( N ) + O ( C ) = O ( N ∗ ( l o g ( N M ) + 1 ) ) O(N)+O(M∗(\frac{N}{M}∗log(\frac{N}{M}))) = O(N)+O(N∗(log(\frac{N}{M})) = O(N)+O(C)= O(N∗(log(\frac{N}{M})+1)) O(N)+O(M(MNlog(MN)))=O(N)+O(N(log(MN))=O(N)+O(C=O(N(log(MN)+1))
当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到 O ( N ) O(N) O(N).

额外空间复杂度: O ( N + M ) O(N + M) O(N+M);桶排序其本身是稳定的,但整体稳定性还需要取决于桶内排序使用的算法。

  • 桶排序的限制:数据很容易分成n个桶,且桶与桶之间有着天然的大小顺序,数据在各个桶内分布均匀。

    img
  • 步骤:

    1、根据数组取值范围"创建"一定数量的"桶"

    2、遍历数组,把每个元素放到对应的"桶"中

    3、当存在“一个桶中有多个元素”的情况时,要先使用合适的排序算法对各个痛内的元素进行排序

    4、按照顺序遍历桶中的每个元素,依次放回数组中

    ("桶"是一种容器,这个容器可以用多种数据结构实现,包括数组、队列或者栈。)

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

typedef struct Node{
    int nValue;
    struct Node *pNext;
}List;

void Sort(List *pHead){
    if (pHead == NULL ||pHead->pNext == NULL) return;
                //根本没有或者只有一个,那都不需要排序,直接return
    //冒泡排序
    List *pNode = pHead;        //趟数
    List *pTemp = NULL;         //遍历
    while(pNode->pNext != NULL){
        pTemp = pHead;
        while (pTemp->pNext != NULL) {
            if(pTemp->nValue > pTemp->pNext->nValue){
                pTemp->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
                pTemp->pNext->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
                pTemp->nValue = pTemp->nValue ^ pTemp->pNext->nValue;
            }
            pTemp = pTemp->pNext;
        }
        pNode = pNode->pNext;
    }
}    

void BucketSort(int arr[],int nLength){
    if(arr == NULL || nLength <= 0)
        return;
    //最大值最小值
    int nMax = arr[0];
    int nMin = arr[0];
    for(int i = 0;i < nLength;i++){
        if(arr[i] > nMax){
            nMax = arr[i];
        }
        if (arr[i] < nMin) {
            nMin = arr[i];
        }
    }
    //计算数字位数
    int nCount = 1;
    int nNum = nMax;
    while(nNum){
        nNum = nNum / 10;   //每次去掉数字最后一位
	    nCount *= 10;           //循环一次计数器+1
    }
    nCount /= 10;

    //拆分高位
    int nMinIndex = nMin / nCount % 10;
    int nMaxIndex = nMax / nCount % 10;

    //申请桶子
    List **pBucket = NULL;
    pBucket = (List**)malloc(sizeof(List*)*(nMaxIndex - nMinIndex + 1));
    memset(pBucket, 0, sizeof(List*)*(nMaxIndex - nMinIndex + 1));

    //元素入桶
    List *pTemp = NULL;
    int nIndex;
    for(int i = 0;i < nLength;i++){
        nIndex = arr[i] / nCount % 10 - nMinIndex;
        //头插
        pTemp = (List*)malloc(sizeof(List));
        pTemp->nValue = arr[i];
        pTemp->pNext = pBucket[nIndex];
        pBucket[nIndex] = pTemp;
    }

    //各桶内元素排序
    for(int i = 0;i < nMaxIndex - nMinIndex + 1;i++){
        Sort(pBucket[i]);
    }

    //将桶内元素放回原数组
    nNum = 0;
    for(int i = 0; i < nMaxIndex - nMinIndex + 1;i++){
        pTemp = pBucket[i];
        while (pTemp) {
            arr[nNum] = pTemp->nValue;
            nNum++;
            pTemp = pTemp->pNext;
        }
    }

    //释放
    List *pDel = NULL;
    for(int i = 0; i < nMaxIndex - nMinIndex + 1;i++){
        pTemp = pBucket[i];
        while (pTemp) {
            pDel = pTemp;
            pTemp = pTemp->pNext;
            free(pDel);
            pDel = NULL;
        }
    }
    free(pBucket);
    pBucket = NULL;
}

void Print(int arr[],int nLength){
    if(arr == NULL || nLength <= 0)
        return;
    for (int i = 0; i < nLength; i++) {
        printf("%d ",arr[i]);
    }
    printf("\n");
}

int main(){
    int arr[] = {101,722,811,119,221,119,662,743,838};
    BucketSort(arr, sizeof(arr)/sizeof(arr[0]));
    Print(arr, sizeof(arr)/sizeof(arr[0]));
    return 0;
}

基数排序(RadixSort)

利用数字规律排序,基于非比较的排序,稳定!

LSD:低位优先

MSD:高位优先

时间复杂度: O ( d ∗ ( n + r ) ) O(d*(n+r)) O(d(n+r))
空间复杂度: O ( n + r ) O(n+r) O(n+r)
稳定性:稳定

  • 设待排序的数组 R [ 1.. n ] R[1..n] R[1..n],数组中最大的数是 d d d位数,基数为 r r r(如基数为10,即10进制,最大有10种可能,即最多需要10个桶来映射数组元素)。处理一位数,需要将数组元素映射到 r r r个桶中,映射完成后还需要收集,相当于遍历数组一遍,则时间复杂度为 O ( n + r ) O(n+r) O(n+r)。所以,总的时间复杂度为 O ( d ∗ ( n + r ) ) O(d*(n+r)) O(d(n+r))

  • 基数排序过程中,用到一个计数器数组,长度为 r r r,以及桶内元素的个数,所以空间复杂度为 O ( n + r ) O(n+r) O(n+r)

  • 基数排序基于分别排序,分别收集,所以是稳定的。

img
  • 步骤:

    1、遍历序列找出最大的数,确定最大的数是几位数;

    2、申请表头(10),按位处理(LSD或者MSD),

    3、按位入桶,采用尾添加,放回

    4、下位重复上一步骤

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

typedef struct Node{
    int nValue;
    struct Node *pNext;
}Radix;  

void RadixSort(int arr[],int nLength){
    if(arr == NULL || nLength <= 0)
        return;
    
    //最大值
    int nMax = arr[0];
    for(int i = 0;i < nLength;i++){
        if(arr[i] > nMax){
            nMax = arr[i];
        }
    }
    
    //计算最大值位数
    int nCount = 1;
    while (nMax) {
        nCount *= 10;
        nMax /= 10;
    }nCount /= 10;
    
    //申请表头
    Radix **pRadix = NULL; 
    pRadix = (Radix**)malloc(sizeof(Radix*)*10);
    memset( pRadix, 0, sizeof(Radix*)*10);
    //按位处理
    for (int i = 1; i <= nCount; i *= 10) {
        //入组
        int nIndex;
        Radix *pTemp = NULL;
        for(int j = 0;j < nLength;j++){
            pTemp = (Radix*)malloc(sizeof(Radix));
            pTemp->nValue = arr[j];
            pTemp->pNext = NULL;
            nIndex = arr[j]/i % 10;
            //尾添加
            if (pRadix[nIndex] == NULL) {
                pRadix[nIndex] = pTemp;
            }else{
                Radix *pNode = pRadix[nIndex];
                while (pNode->pNext != NULL) {
                    pNode = pNode->pNext;
                }
                pNode->pNext = pTemp;
            }
        }
        
        //放回原数组
        int k = 0;
        for(int i = 0; i < 10;i++){
            pTemp = pRadix[i];
            while (pTemp) {
                arr[k] = pTemp->nValue;
                k++;
                pTemp = pTemp->pNext;
            }
        }
        //释放
        Radix *pDel = NULL;
        for(int i = 0; i < 10;i++){
            pTemp = pRadix[i];
            while (pTemp) {
                pDel = pTemp;
                pTemp = pTemp->pNext;
                free(pDel);
                pDel = NULL;
            }
        }
        //表头清空
        memset(pRadix, 0, sizeof(Radix*)*10);
    }
    free(pRadix);
    pRadix = NULL;
}

void Print(int arr[],int nLength){
    if(arr == NULL || nLength <= 0)
        return;
    for (int i = 0; i < nLength; i++) {
        printf("%d ",arr[i]);
    }
    printf("\n");
}

int main(){
    int arr[] = {11,72,811,1,32,17,22,156,11};
    RadixSort(arr, sizeof(arr)/sizeof(arr[0]));
    Print(arr, sizeof(arr)/sizeof(arr[0]));
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

97Marcus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值