数据结构——计数、桶、基数排序

news2024/11/30 6:38:16

目录

引言

计数排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

桶排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

基数排序

1.算法思想

2.算法步骤

3.代码实现

4.复杂度分析

排序算法的稳定性

1.稳定性的概念

2.各个排序算法的稳定性

结束语


引言

目前写的排序:

数据结构——冒泡、选择、插入和希尔排序

数据结构——快速排序

数据结构——归并排序

数据结构——堆排序

有需要的可以看看。

我们接下来学习一下计数排序、桶排序、基数排序。

计数排序

计数排序(Counting Sort)是一种非基于比较的排序算法,由Harold H. Seward在1954年提出。该算法的主要优势在于其线性时间复杂度,特别适用于待排序元素为整数且范围较小的情况。

1.算法思想

计数排序的基本思想是统计数组中每个元素的出现次数,然后利用这些信息将原始序列重新组合成有序序列。它并不直接比较元素之间的大小,而是通过计算小于等于某个值的元素个数来确定该元素在排序后数组中的位置。

2.算法步骤

1.找出待排序数组中的最大值和最小值

2.创建计数数组:计数数组的长度通常设置为待排序数组中的最大值加1(如果已知范围,则可以直接设定长度为范围大小)。计数数组的初始值全部设为0。

3.统计元素出现次数:遍历待排序数组,对于数组中的每个元素,在计数数组对应下标的位置上加1,以统计该元素的出现次数。

4.修改计数数组:对计数数组进行前缀和操作,即每个位置的值变为它本身加上前一个位置的值。这样,计数数组的每个位置就代表了小于等于该下标值的元素在排序后数组中的最后位置。

5.重建输出数组:从后向前遍历待排序数组,根据计数数组中的值确定当前元素在输出数组中的位置,并更新计数数组。具体地,对于待排序数组中的每个元素,找到计数数组中对应下标的值(即该元素在排序后数组中的位置),将元素放到输出数组的相应位置,并将计数数组中对应下标的值减1。

6.输出排序后的数组:此时,输出数组就是排序后的数组。

动图演示如下:

3.代码实现

void CountSort(int* arr, int n)
{
    // 初始化最小值和最大值为数组的第一个元素  
    int min = arr[0];
    int max = arr[0];

    // 遍历数组,找到最小值和最大值  
    for (int i = 1; i < n; i++)
    {
        if (arr[i] < min)
            min = arr[i]; // 更新最小值  
        if (arr[i] > max)
            max = arr[i]; // 更新最大值  
    }

    // 计算值的范围(即最大值和最小值之间的差值加1)  
    int range = max - min + 1;

    int* count = (int*)calloc(range, sizeof(int));
    if (count == NULL)
    {
        perror("calloc fail:");
        return;
    }

    // 统计每个元素的出现次数  
    // 通过数组下标转换(arr[i] - min)将原始数组的值
    // 映射到计数数组的索引上  
    for (int i = 0; i < n; i++)
    {
        count[arr[i] - min]++;
    }

    // 根据计数数组中的计数,重建排序后的数组  
    // 遍历计数数组,每次将对应计数值数量的元素放到arr中  
    int index = 0; 
    for (int i = 0; i < range; i++)
    {
        // 当count[i]大于0时,说明有i+min这个值的元素需要被放置  
        while (count[i]--)
        {
            arr[index++] = i + min; // 放置元素,并更新index  
        }
    }

    free(count);
    count = NULL; 
}

测试一下:

4.复杂度分析

时间复杂度:由于遍历了原数组与range数组,因此时间复杂度为O(n+range)。

空间复杂度:开辟了大小为range的数组,所以空间复杂度为O(range)。

桶排序

桶排序(Bucket Sort)是一种基于分配策略的排序算法。

1.算法思想

桶排序的算法思想是将数组中的元素分配到有限数量的桶(或称为箱)里,每个桶再分别进行排序(可能使用其他排序算法或递归使用桶排序),最后合并成结果序列。

2.算法步骤

1.初始化桶:首先,需要确定桶的数量及每个桶对应的数据范围。这通常与待排序数据的范围有关,目的是使每个桶尽可能均匀地包含一部分数据。

2.分配元素:遍历待排序序列,将每个元素根据其值放入对应的桶中。这一步相当于对数据进行初步的划分和聚集。

3.桶内排序:对每个非空桶内部使用其他排序算法(如插入排序、快速排序等)进行排序,确保每个桶内的数据有序。

4.合并桶:按照桶的顺序,依次从每个桶中取出元素,合并成一个有序序列,即完成整个数据集的排序。

动图演示:

PS:这里的位数桶只是方便演示。

3.代码实现

桶内的排序我们同样可以借助插入排序来实现。

代码如下:

#define BUCKET_SIZE 10  // 定义桶的数量  
#define ARRAY_SIZE 30   // 定义数组的大小  

void InsertSort(int* arr, int n)
{
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = arr[end + 1];
        while (end >= 0)
        {
            if (tmp < arr[end])
            {
                arr[end + 1] = arr[end];
                --end;
            }
            else
            {
                break;
            }
        }
        arr[end + 1] = tmp;
    }
}

// 桶排序函数  
void bucketSort(int arr[], int n) 
{
    int bucket[BUCKET_SIZE][ARRAY_SIZE], max = arr[0], min = arr[0];
    int i, j, k;
    int b_size[BUCKET_SIZE] = { 0 }; // 每个桶的元素数量  

    // 找到数组中的最大值和最小值  
    for (i = 1; i < n; i++) 
    {
        if (arr[i] > max)
            max = arr[i];
        if (arr[i] < min)
            min = arr[i];
    }

    // 计算桶的间隔  
    int range = max - min;
    int interval = range / BUCKET_SIZE; 

    // 将数据分配到各个桶中  
    for (i = 0; i < n; i++) 
    {
        int bi = (arr[i] - min) / interval; // 使用整数除法来分配桶  
        if (bi >= BUCKET_SIZE)
        {
            bi = BUCKET_SIZE - 1;
        }
        bucket[bi][b_size[bi]++] = arr[i];
    }

    // 对每个桶进行排序  
    for (i = 0; i < BUCKET_SIZE; i++)
    {
        InsertSort(bucket[i], b_size[i]);
    }

    // 合并桶中的数据  
    k = 0;
    for (i = 0; i < BUCKET_SIZE; i++)
    {
        for (j = 0; j < b_size[i]; j++)
        {
            arr[k++] = bucket[i][j];
        }
    }
}

测试一下:

4.复杂度分析

时间复杂度:

(1)如果数据均匀分布在桶中,且每个桶内能快速排序(如计数排序、插入排序等),则平均时间复杂度可以达到O(n + k),其中n是元素总数,k是桶的数量。这是因为在理想情况下,每个桶只需要处理少量元素。

(2)当输入数据已经完全有序或者极度集中在一个桶内,桶排序会退化为线性查找,此时时间复杂度为O(n^2)。

空间复杂度:由于需要额外存储每个桶以及额外的空间用于最终结果的合并,因此空间复杂度通常是O(n + k)。

基数排序

基数排序(Radix Sort)是一种非比较型整数排序算法,它通过键值的各个位的值,将要排序的元素分配到某些“桶”中,以达到排序的目的。基数排序也被称为“分配式排序”或“桶子法”(bucket sort),是桶排序的扩展。

1.算法思想

基数排序的基本原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。具体来说,它首先将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

2.算法步骤

1.计算最大数位数:首先找出待排序数组中最大数的位数。

2.统一数位长度:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。

3.按位排序:从最低位开始,依次进行一次排序。排序时,根据当前位的数值,将元素分配到对应的桶中。

4.合并桶中元素:将各个桶中的元素依次取出,合并成一个新的有序数组。

5.重复步骤3和4:对更高位进行排序,直到最高位排序完成。

3.代码实现

// 获取数字num在exp位上的数字
int Get_digit(int num, int exp) 
{
    return (num / exp) % 10;
}

// 对数组arr中的数字按exp位进行计数排序
void CountSortDigit(int* arr, int n, int exp) 
{
    // 分配一个大小为10的数组来计数0-9每个数字出现的次数
    int* counter = (int*)malloc(sizeof(int) * 10);
    if (counter == NULL) 
    {
        perror("malloc fail:");
        return;
    }
    // 初始化计数器数组
    memset(counter, 0, sizeof(int) * 10);
    // 遍历数组,计算每个数字在exp位上出现的次数
    for (int i = 0; i < n; i++) 
    {
        int x = Get_digit(arr[i], exp);
        counter[x]++;  
    }
    // 累积计数,使counter[i]包含小于或等于i的数字的总数
    for (int i = 1; i < 10; i++) 
    {
        counter[i] += counter[i - 1];
    }
    // 分配一个与原始数组相同大小的临时数组来存储排序后的结果
    int* ret = (int*)malloc(sizeof(int) * n);  
    if (ret == NULL) 
    {
        perror("malloc fail:");
        return;
    }
    // 从后往前遍历原始数组,根据exp位上的数字将元素放入ret数组的适当位置
    for (int i = n - 1; i >= 0; i--) 
    {
        int d = Get_digit(arr[i], exp);
        ret[counter[d] - 1] = arr[i];  
        counter[d]--;
    }
    // 将排序后的结果复制回原始数组
    for (int i = 0; i < n; i++) 
    {
        arr[i] = ret[i];
    }
    free(ret);  
    free(counter);  
}

void RadixSort(int* arr, int n) 
{
    // 找到数组中的最大值,以确定最大的位数
    int max = arr[0];
    for (int i = 1; i < n; i++) 
    {  
        if (arr[i] > max) 
        {
            max = arr[i];
        }
    }
    // 从个位开始,逐步向高位进行排序  
    // exp是当前的位数(1代表个位,10代表十位,依此类推)
    for (int exp = 1; max / exp > 0; exp *= 10) 
    {
        // 对当前位数进行计数排序
        CountSortDigit(arr, n, exp);
    }
}

测试一下:

4.复杂度分析

时间复杂度:数据量为N、数据为D进制、最大位数为K。时间复杂度可以表示为O((N + D) * K)。但在实际应用中,由于D和K都相对较小,我们通常会将其简化为O(N * K),并进一步简化为O(N)。

空间复杂度:由于基数排序需要借助长度为N和D的统计数组,因此基数排序空间复杂度为O(N+D)。

排序算法的稳定性

1.稳定性的概念

排序算法的稳定性是指算法在排序过程中,对于具有相同关键字的元素,能否保持它们在原序列中的相对顺序不变。如果保持不变,则称该排序算法是稳定的;如果相对顺序可能发生变化,则称该排序算法是不稳定的。

2.各个排序算法的稳定性

排序算法平均时间复杂度最优时间复杂度最差时间复杂度空间复杂度稳定性
冒泡排序O(n^2)o(n)o(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(n^1.3)O(n)O(n^2)O(log n)不稳定
快速排序O(n log n)O(n log n)O(n log n)O(n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(1)稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
计数排序O(n+range)O(n+range)O(n+range)O(range)稳定
桶排序O(n + k)O(n + k)O(n^2)O(n + k)稳定
基数排序O(N * K)O(N * K)O(N * K)O(N + K)稳定

结束语

排序这部分大概就学到这里。

求点赞收藏评论关注!!!

感谢各位大佬的支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初识Linux · 自主Shell编写

目录 前言&#xff1a; 1 命令行解释器部分 2 获取用户命令行参数 3 命令行参数进行分割 4 执行命令 5 判断命令是否为内建命令 前言&#xff1a; 本文介绍是自主Shell编写&#xff0c;对于shell&#xff0c;即外壳解释程序&#xff0c;我们目前接触到的命令行解释器&am…

基于vue框架的大学生四六级学习网站设计与实现i8o8z(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,训练听力,学习单词,单词分类,阅读文章,文章类型,学习课程 开题报告内容 基于Vue框架的大学生四六级学习网站设计与实现开题报告 一、研究背景与意义 随着全球化进程的加速和国际交流的日益频繁&#xff0c;英语作为国际通用语言…

22.3 解读k8s服务发现源码

本节重点介绍 : discovery.Manager服务发现管理员 注册各个服务发现源启动各个服务发现源处理服务发现的结果 k8s服务发现 k8s-client informer机制 架构图补充 注册各个服务发现源 位置 D:\go_path\src\github.com\prometheus\prometheus\discovery\manager.go去掉部分细节…

ConcurrentHashMap 中的并行性

ConcurrentHashMap 在多线程应用程序中被广泛使用。多线程应用程序的示例包括在线游戏应用程序、聊天应用程序&#xff0c;它为应用程序增加了并发性的好处。为了使应用程序本质上更具并发性&#xff0c;ConcurrentHashMap 引入了一个名为“并行性”的概念。 在本文中&#xf…

飞机导航数据库资料

以上是从网上收集的飞机导航数据库的一些资料。现在放在百度网盘中。 链接&#xff1a;https://pan.baidu.com/s/1fDYuaB0DuyKmYt6C_lXvZQ?pwdkcqj 提取码&#xff1a;kcqj

ZTE RRC重建优化案例

ZTE RRC重建优化案例 随着移动通信网络的不断发展&#xff0c;用户对网络的稳定性和覆盖质量提出了更高的要求。尤其在LTE网络中&#xff0c;RRC&#xff08;Radio Resource Control&#xff09;连接的稳定性直接影响用户体验和业务连续性。然而&#xff0c;在实际网络环境中&a…

案例-表白墙简单实现

文章目录 效果展示初始画面提交内容后画面&#xff08;按键按下&#xff09; 代码区 效果展示 初始画面 提交内容后画面&#xff08;按键按下&#xff09; 代码区 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">…

C++输⼊输出

1.<iostream> 是 Input Output Stream 的缩写&#xff0c;是标准的输⼊、输出流库&#xff0c;定义了标准的输⼊、输 出对象 2.std::cin 是 istream 类的对象&#xff0c;它主要⾯向窄字符&#xff08;narrow characters (of type char)&#xff09;的标准输 ⼊流。 3…

STL之priority_queue篇——深入剖析C++中优先队列的实现原理、核心特性及其底层机制

文章目录 前言一、补充内容&#xff1a;堆1.1 什么是堆1.2 堆的分类与性质1.3 堆的向下调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.4 堆的向上调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.5 数组建堆算…

eclpsexxx

Copyright?2001-2004 International Business Machines Corp. Guidelines Eclipse 用户界面指南 2.1 版 查看目录 作者&#xff1a;Nick Edgar, Kevin Haaland, Jin Li , Kimberley Peter 译者&#xff1a;Bobbie Wang&#xff0c;Qi Liang 最新更新: 2004年2月 注意 您…

kaggle实战2信用卡反欺诈逻辑回归模型案例1

信用卡欺诈案例 数据集下载地址 https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv 参考不平衡数据的分类 文章目录 只进行特征衍生&#xff0c;未进行数据标准化、上才样处理数据不平衡问题&#xff0c;得到的准确率和召回率居然很高如果不处理数据…

李宏毅 X 苹果书 自注意力机制 学习笔记上

self attention 是一种network架构使用场景&#xff1a;输入一组向量&#xff0c;这组向量的性质&#xff1a;数量有变化&#xff0c;序列长度不一 模型输入 文字处理&#xff1a; 模型输入&#xff1a;句子&#xff08;句子的长度&#xff0c;单词都不一样&#xff09;&am…

qt QMainWindow 自定义标题栏

可以使用setMenuWidget 来将自定义的标题栏 QWidget 设置进去就可以&#xff0c; 用来替代setMenu 菜单栏单一&#xff0c;自定义不高的问题

node_exporter使用textfile collector收集业务数据

上一篇文章讲了使用Pushgateway收集业务数据的方法&#xff0c;今天讲另外一种方式textfile collector The textfile collector is similar to the Pushgateway, in that it allows exporting of statistics from batch jobs. The Pushgateway should be used for service-leve…

解决ModuleNotFoundError: No module named ‘torchcrf‘

运行深度学习程序时候&#xff0c;出现报错&#xff1a;ModuleNotFoundError: No module named torchcrf 将 from torchcrf import CRF 改为 from TorchCRF import CRF

无设计器简单实例

目录 1、界面设计Qt5元对象系统1. **QObject 类**2. **QMetaObject**3. **信号和槽机制**4. **宏&#xff1a;Q_OBJECT**5. **动态属性**6. **反射机制**7. **元对象编译器&#xff08;MOC&#xff09;** 2、完成程序功能 1、界面设计 不点创建界面 在dialog.h中 #ifndef DIA…

树莓派5里使用protobuf

由于现在protobuf越来越复杂了&#xff0c;自己去编译&#xff0c;还是比较麻烦。 比如最新的V28版本&#xff0c;就会要求使用cmake或者bazel来编译了。 如果不要求使用最新的版本&#xff0c;直接使用系统里带的版本也是可以的。 可以进行如下操作&#xff1a; sudo apt …

【算法系列-链表】交换链表节点(反转 + 交换)

【算法系列-链表】交换链表节点(反转 交换) 文章目录 【算法系列-链表】交换链表节点(反转 交换)1. 反转链表1.1 思路分析&#x1f3af;1.2 解题过程&#x1f3ac;1.3 代码示例&#x1f330; 2. 两两交换链表中的节点2.1 思路分析&#x1f3af;2.2 解题过程&#x1f3ac;2.3 …

电器自动化入门08:隔离变压器、行程开关介绍及选型

视频链接&#xff1a;3.4 电工知识&#xff1a;三相交流异步电动机自动往返行程控制及控制变压器选型_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1PJ41117PW?p8&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.隔离&#xff08;控制&#xff09;变压器 2.行程开…

C++_智能指针详解

什么是智能指针&#xff1f;为什么要有智能指针&#xff1f;到目前为止&#xff0c;我们编写的程序所使用的对象都有着严格定义的生命周期。比如说&#xff0c;全局对象在程序启动时分配&#xff0c;在程序结束时销毁&#xff1b;再比如说局部static对象在第一次使用前分配&…