常用的内部排序方法-非比较排序

这篇文章中我们来探讨一下常用的非比较排序算法:计数排序基数排序桶排序。在一定条件下,它们的时间复杂度可以达到O(n)。

  这里我们用到的唯一数据结构就是数组,当然我们也可以利用链表来实现下述算法。

 

 

  计数排序(Counting Sort)

 

  计数排序用到一个额外的计数数组C,根据数组C来将原数组A中的元素排到正确的位置。

  通俗地理解,例如有10个年龄不同的人,假如统计出有8个人的年龄不比小明大(即小于等于小明的年龄,这里也包括了小明),那么小明的年龄就排在第8位,通过这种思想可以确定每个人的位置,也就排好了序。当然,年龄一样时需要特殊处理(保证稳定性),通过反向填充目标数组,填充完毕后将对应的数字统计递减,可以确保计数排序的稳定性。算法的步骤如下:

  1. 统计数组A中每个值A[i]出现的次数,存入C[A[i]]
  2. 从前向后,使数组C中的每个值等于其与前一项相加,这样数组C[A[i]]就代表了数组A中小于等于A[i]的元素个数
  3. 反向填充目标数组B:将数组元素A[i]放在数组B的第C[A[i]]项(即B[C[A[i]] - 1]),每放一个元素就将C[A[i]]递减

 

  计数排序的实现代码如下:

复制代码
#include<iostream>
using namespace std;

// 分类 ------------ 内部非比较排序
// 数据结构 --------- 数组
// 最差时间复杂度 ---- O(n + k)
// 最优时间复杂度 ---- O(n + k)
// 平均时间复杂度 ---- O(n + k)
// 最差空间复杂度 ---- O(n + k)

const int k = 100;   // 基数为100,排序[0,99]内的整数
int C[k];            // 计数数组

void countingsort(int A[], int n)
{
    for (int i = 0; i < k; i++)    // 初始化,将数组C中的元素置0(此步骤可省略,整型数组元素默认值为0)
    {
        C[i] = 0;
    }
    for (int i = 0; i < n; i++)    // 使C[i]保存着等于i的元素个数
    {
        C[A[i]]++;
    }
    for (int i = 1; i < k; i++)    // 使C[i]保存着小于等于i的元素个数,排序后元素i就放在第C[i]个输出位置上
    {
        C[i] = C[i] + C[i - 1];
    }
    int *B = (int *)malloc((n)* sizeof(int));// 分配临时空间,长度为n,用来暂存中间数据
    for (int i = n - 1; i >= 0; i--) // 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
    {                                
        B[C[A[i]] - 1] = A[i];        // 把每个元素A[i]放到它在输出数组B中的正确位置上
        C[A[i]]--;                    // 当再遇到重复元素时会被放在当前元素的前一个位置上保证计数排序的稳定性
    }
    for (int i = 0; i < n; i++)       // 把临时空间B中的数据拷贝回A
    {
        A[i] = B[i];
    }
    free(B);    // 释放临时空间 
}

int main()
{
    int A[] = { 15, 22, 19, 46, 27, 73, 1, 19, 8 };  // 针对计数排序设计的输入,每一个元素都在[0,100]上且有重复元素
    int n = sizeof(A) / sizeof(int);
    countingsort(A, n);
    printf("计数排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ",A[i]);
    }
    printf("\n");
    return 0;
}
复制代码

 

  下图给出了对{ 4, 1, 3, 4, 3 }进行计数排序的简单演示过程

  

  计数排序的时间复杂度和空间复杂度取决于数组A的数据范围(等于A中元素的最大值与最小值的差加上1),因此对于数据范围很大的数组,计数排序需要大量时间和内存。

  例如:对0到99之间的数字进行排序,计数排序是最好的算法,然而计数排序并不适合按字母顺序排序人名,将计数排序用在基数排序算法中,能够更有效的排序数据范围很大的数组。

 

 

  基数排序(Radix Sort)

 

  基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机上的贡献。它是这样实现的:将所有待比较正整数统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始进行基数为10的计数排序,一直到最高位计数排序完后,数列就变成一个有序序列(利用了计数排序的稳定性)。

  基数排序的实现代码如下:

复制代码
#include<iostream>
using namespace std;

// 分类 -------------- 内部非比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n * dn)
// 最差空间复杂度 ---- O(n * dn)

const int dn = 3;                // 本程序排序的元素为三位数(含以下)
const int radix = 10;            // 基数为10,对[0,9]的数字进行计数排序
int C[radix];

int getdigit(int x, int d)            // 获得元素x的第d位数字
{
    int radix[] = { 1, 1, 10, 100 };  // 最大为三位数,所以这里只要到百位就满足了
    return (x / radix[d]) % 10;
}

void countingsort(int A[], int n, int B[], int d)// 应用计数排序(依据元素的第d位数字)对元素排序
{
    for (int i = 0; i < radix; i++)    // 初始化,将数组C中的元素置0
    {
        C[i] = 0;
    }
    for (int i = 0; i < n; i++)        // 使C[i]保存着当前位等于i的元素个数
    {
        C[getdigit(A[i], d)]++;
    }
    for (int i = 1; i < radix; i++)    // 使C[i]保存着当前位小于等于i的元素个数,排序后元素i就放在第C[i]个输出位置上
    {
        C[i] = C[i] + C[i - 1];
    }
    for (int i = n - 1; i >= 0; i--)   // 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
    {
        int j = getdigit(A[i], d);     // 元素A[i]当前位数字为j   
        B[C[j] - 1] = A[i];            // 根据当前位数字,把每个元素A[i]放到它在输出数组B中的正确位置上
        C[j]--;                        // 当再遇到当前位数字同为j的元素时,会将其放在当前元素的前一个位置上保证计数排序的稳定性
    }
}

void lsd_radixsort(int A[], int n)         // 最低位优先基数排序
{
    int *B = (int*)malloc(n * sizeof(int));// 分配临时空间,长度为n,用来暂存中间数据
    for (int d = 1; d <= dn; d++)          // 从最低位开始到最高位
    {
        countingsort(A, n, B, d);          // 依据第d位调用计数排序
        for (int i = 0; i < n; i++)        // 把临时空间B中的数据拷贝回A,针对更高位的计数排序在此基础上继续对元素排序
        {
            A[i] = B[i];
        }
    }
    free(B);
}

int main()
{
    int A[] = { 20, 90, 64, 289, 998, 365, 852, 123, 789, 456 };// 针对基数排序设计的输入
    int n = sizeof(A) / sizeof(int);
    lsd_radixsort(A, n);
    printf("基数排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}
复制代码

 

  下图给出了对{ 329, 457, 657, 839, 436, 720, 355 }进行基数排序的简单演示过程

  

  基数排序的时间复杂度是O(n * dn),其中n是排序元素个数,dn是数字位数。这个时间复杂度不一定优于O(n log n),dn的大小取决于数字位的选择(比如比特位数),和待排序数据所属数据类型的全集的大小;dn决定了进行多少轮处理,而n是每轮处理的操作数目。

  如果考虑和比较排序进行对照,基数排序的形式复杂度虽然不一定更小,但由于不进行比较,因此其基本操作的代价较小,而且如果适当的选择基数,dn一般不大于log n,所以基数排序一般要快过基于比较的排序,比如快速排序。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序并不是只能用于整数排序。

 

 

  桶排序(Bucket Sort)

 

  桶排序也叫箱排序。工作的原理是将数组元素映射到有限数量个桶里,利用计数排序可以定位桶的边界,每个桶再各自进行桶内排序(使用其它排序算法或以递归方式继续使用桶排序)。

  桶排序的实现代码如下:

复制代码
#include<iostream>
using namespace std;

// 分类 ------------- 内部非比较排序
// 数据结构 ---------- 数组

/* 本程序用数组模拟桶 */
const int bn = 5;                 // 我们这里打算使用5个桶
int C[bn];                        // 计数数组,存放桶的边界信息

int MapToBucket(int x, int max)   // 把元素x映射到对应的桶中
{
    return (4 * x) / max;         // 返回值范围{0,1,2,3,4},共5个桶
}

void insertionsort(int A[], int left, int right)// 同一个桶内进行插入排序
{
    for (int i = left + 1; i <= right; i++)     // 从桶内第二张牌开始抓,直到最后一张牌
    {
        int get = A[i];
        int j = i - 1;
        while (j >= left && A[j] > get)
        {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = get;
    }
}

void countingsort(int A[], int n, int B[])// 利用计数排序定位各个桶的边界
{
    for (int i = 0; i < bn; i++)       // 初始化,将数组C中的元素置0
    {
        C[i] = 0;
    }
    int max = A[0];
    for (int i = 1; i < n; i++)        // 获得数组A中元素的最大值,用于把输入元素向桶中映射
    {
        if (A[i] > max)
            max = A[i];
    }
    for (int i = 0; i < n; i++)        // 使C[i]保存着i号桶中元素的个数
    {
        C[MapToBucket(A[i], max)]++;
    }
    for (int i = 1; i < bn; i++)       // 定位每个桶的左边界索引:C[i]为i+1号桶中第一个元素的数组下标
    {
        C[i] = C[i] + C[i - 1];
    }
    for (int i = n - 1; i >= 0; i--)   // 从后向前扫描保证计数排序的稳定性(重复元素相对次序不变)
    {
        int j = MapToBucket(A[i], max);// 元素A[i]位于第j号桶
        B[C[j] - 1] = A[i];            // 把每个元素A[i]放到它在输出数组B中的正确位置上
        C[j]--;                        // 当再遇到同一个桶中的元素时会被放在当前元素的前一个位置上
    }
}

void bucketsort(int A[], int n)
{
    int *B = (int *)malloc((n) * sizeof(int)); // 分配临时空间,长度为n,用来暂存中间数据
    countingsort(A, n, B);       // 应用计数排序把不同桶中的元素排好序,同一桶中的元素暂时按输入次序存放
    for (int i = 0; i < n; i++)  // 把临时空间B中的数据拷贝回A
    {
        A[i] = B[i];
    }
    free(B);                    // 释放临时空间  
    for (int i = 0; i < bn - 1; i++) // 对同一个桶中的元素应用插入排序,0号筒必定只有一个元素,无需排序
    {
        int left = C[i];         // C[i]为i+1号桶中第一个元素的数组下标
        int right = C[i + 1] - 1;// C[i+1]-1为i+1号桶中最后一个元素的数组下标   
        if (left < right)         // 对非空的桶进行桶内排序
            insertionsort(A, left, right);
    }
}

int main()
{
    int A[] = { 29, 25, 3, 49, 9, 37, 21, 43 };// 针对桶排序设计的输入
    int n = sizeof(A) / sizeof(int);
    bucketsort(A, n);
    printf("桶排序结果:");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
    return 0;
}
复制代码

 

 

   下图给出了对{ 29, 25, 3, 49, 9, 37, 21, 43 }进行桶排序的简单演示过程

  

 

  桶排序不是比较排序,不受到O(nlogn)下限的影响,它是鸽巢排序的一种归纳结果,当所要排序的数组值分散均匀的时候,桶排序拥有线性的时间复杂度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值