C++算法:排序之四(计数、基数、桶排序)

news2024/12/28 5:24:42

C++算法:排序

排序之一(插入、冒泡、快速排序)
排序之二(归并、希尔、选择排序)
排序之三(堆排序)
排序之四(计数、基数、桶排序)


文章目录

  • C++算法:排序
  • 三、非比较排序算法
    • 8、计数排序
    • 9、基数排序
    • 10、桶排序
  • 总结
  • 原创文章,未经许可,严禁转载


本文续:C++算法:排序之三(堆排序)


三、非比较排序算法

非比较排序是一种不通过比较来决定元素间的相对次序的排序算法。它可以突破基于比较排序的时间下界,以线性时间运行,需要开辟额外的存储空间,因此也称为线性时间非比较类排序。常见的非比较排序算法包括桶排序、计数排序和基数排序。这些算法都不直接比较元素大小,而是通过计算每个元素有几个,应该在什么位置来实现的。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以非比较排序也不是只能使用于整数。

8、计数排序

计数排序是一种非比较型整数排序算法。它的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。它不是基于比较的排序算法,它不用循环的去一次次直接对比所有元素,所以时间复杂度较好。

还是用个例子说明一下,这个排序法用来对成绩之类的大小元素差很有限的,且重复元素很多的情况是非常好用的,比任何排序法都快。

一个简单的计数排序的例子:

  • 假设我们有一个数组 [4, 3, 2, 1, 4, 3, 2, 4],我们想要对它进行升序排序。
  • 首先,找出数组中的最大值和最小值,这里是 4 和 1。
  • 然后,创建一个计数数组,长度为最大值减最小值加1,即 [0, 0, 0, 0]。
  • 接下来,遍历原数组,统计每个元素出现的次数。遍历完成后,计数数组变为 [1, 2, 2, 3]。
  • 然后,从头开始遍历计数数组,将元素依次填充回原数组。最终得到排序后的数组 [1, 2, 2, 3, 3, 4, 4, 4]。
    在这里插入图片描述

代码如下(与动图示例过程一致):

#include <iostream>
#include <vector>

using namespace std;

void count_sort(vector<int> &vec){  
    int len = vec.size();
    int idx = 0, mini, maxi;
    mini = vec[0];           //最小元素
    maxi = vec[0];           //最大元素
    for (int i=0; i<len; ++i){
        if (vec[i] > maxi){
            maxi = vec[i];
        } else if (vec[i] < mini){
            mini = vec[i];
        }
    }
    int k = maxi - mini + 1;     //放置每个元素数量的数组size
    int* arr = new int[k];       //生成数组
    for (int i=0; i<k; i++) arr[i] = 0;          //填充0
    for (int i=0; i<len; i++) arr[vec[i]-mini]++; //vec[i]-mini就是这个元素对应计数数组的下标
    for (int i=0; i<k; i++){   //把数组排序还原回去
        while (arr[i]-- >0){
            vec[idx++] = i + mini;  //idx是要排数组vec的下标,i+mini是vec[i]-mini的反向还原
        }
    }
    delete[] arr;
}

int main(){
    vector<int> vec = {2,3,8,7,1,2,2,2,7,3,9,8,2,1,4,2,4,6,9,2};
    count_sort(vec);
    for (auto it=vec.begin(); it!=vec.end(); it++){
        cout << *it << " ";
    }
    return 0;
}

这个代码逻辑清晰、简单易懂,就不再解释了,看看注释也就明白了。从这个计数排序的逻辑来看,它有一个很好的作用:将大数据转换为小数据来进行排序。明显小数据比较要比大数据快很多,很多要求对大量数据排序又要求低内存使用的情况可以考虑。内存不够它还可以分段。

9、基数排序

基数排序也是一种非比较型整数排序算法。它的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序不是直接根据元素整体的大小进行元素比较,而是将原始列表元素分成多个部分,对每一部分按一定的规则进行排序,进而形成最终的有序列表。说人话就一句话:分个位、十位 … 单独比较。
在这里插入图片描述

代码如下(示例):

include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void radix_sort(vector<int>& vec){
    const int base = 10;
    vector<int> temp(vec.size());
    int max_val = *max_element(vec.begin(), vec.end());
    for (int exp = 1; max_val / exp > 0; exp *= base){
        vector<int> count(base);
        for (int i = 0; i < vec.size(); i++){
            count[(vec[i] / exp) % base]++;
        }
        for (int i = 1; i < base; i++){
            count[i] += count[i - 1];
        }
        for (int i = vec.size() - 1; i >= 0; i--){
            temp[count[(vec[i] / exp) % base] - 1] = vec[i];
            count[(vec[i] / exp) % base]--;
        }
        vec = temp;
    }
}

int main(){
    vector<int> vec = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    radix_sort(vec);
    for (auto it=vec.begin(); it!=vec.end(); it++){
        cout << *it << " ";
    }
    return 0;
}

在这个例子中,我们使用了LSD(Least significant digital)的基数排序方式,即从最低位开始进行排序。

首先,我们定义了一个常量base,表示十进制。

然后,我们定义了一个临时向量temp,用于存储排序过程中的中间结果。

max_element函数用来找到向量中的最大值,赋值给变量max_val。

接下来是一个外层循环,它从最低位开始,依次对每一位进行排序。循环变量exp表示当前正在处理的位数。循环条件是max_val / exp > 0,表示只要还有更高位就继续循环。

在外层循环内部,我们首先定义了一个计数器向量count,用于存储每个桶中元素的个数。然后遍历输入向量中的每个元素,并根据当前位数更新计数器向量。

接着,我们对计数器向量进行前缀和处理,使得count[i]表示小于等于i的元素个数。

然后再次遍历输入向量中的每个元素,并根据计数器向量将它们放入临时向量中正确的位置上。

最后,将临时向量复制回输入向量,并更新循环变量exp。

外层循环结束后,输入向量就已经排好序了。这个排序用处不是太大,代码不长逻辑却比较绕。只适用于整数,字符串可以表示为整数。时间复杂度为O(kn),k是整数最大位数。空间复杂度和时间复杂度一样。

10、桶排序

桶排序也是一种非比较排序算法。它不受到O(N*logN)下限的影响。桶排序的思想近乎彻底的分治思想。桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个子范围(桶)。它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。没找到合适的动图,不过下图也很能说明排序原理了:

在这里插入图片描述

代码如下(示例):

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

using namespace std;

void insert_sort(vector<int>& arr){
    for (int i = 1; i < arr.size(); i++){
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

void bucket_sort(vector<int>& arr){
    int n = arr.size();
    int max_val = *max_element(arr.begin(), arr.end());
    int min_val = *min_element(arr.begin(), arr.end());
    int range = max_val - min_val + 1;
    int bucket_size = ceil(range / n);
    vector<vector<int>> buckets(n+1);
    for (int i = 0; i < n; i++) {
        int index = (arr[i] - min_val) / bucket_size;
        buckets[index].push_back(arr[i]);
    }
    for (int i = 0; i < n; i++){
        insert_sort(buckets[i]);
    }
    int index = 0;
    for (int i = 0; i < n+1; i++){
        for (int j = 0; j < buckets[i].size(); j++){
            arr[index++] = buckets[i][j];
        }
    }
}

int main(){
    vector<int> vec = {37,18,21,49,0,25,6,14};
    bucket_sort(vec);
    for (auto it=vec.begin(); it!=vec.end(); it++){
        cout << *it << " ";
    }
    return 0;
}

代码中,bucket_sort 函数首先计算出待排序数组中的最大值和最小值,以及它们之间的范围。然后根据数组的大小和范围计算出每个桶的大小和桶的数量。接下来,遍历数组中的每个元素,根据元素的值计算出它应该放入哪个桶中,并将其放入对应的桶中。然后对每个桶中的数据进行插入排序。最后,将所有桶中的数据依次取出并放回原数组中,即可得到一个有序序列。

桶排序主要适用于输入数据均匀分布在一个范围内的情况。它是计数排序的扩展版本,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素。通过映射函数将待排序数组中的元素映射到各个对应的桶中,然后对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。


总结

这些算法各有优缺点,适用于不同的场景。一般来说,冒泡排序、选择排序和插入排序适用于小规模的数据,因为它们的时间复杂度较高,但是空间复杂度较低,且易于实现。

希尔排序是对插入排序的改进,通过增加间隔来减少比较次数,适用于中等规模的数据。

归并排序和快速排序是分治法的典型应用,它们可以将大规模的数据分成小块进行排序,然后再合并,适用于大规模的数据,但是需要额外的空间。

堆排序是利用堆这种数据结构来实现的,它可以在不使用额外空间的情况下,对大规模的数据进行排序,但是比较次数较多,且不稳定。

计数排序、桶排序和基数排序是非比较型的算法,它们可以在线性时间内对特定范围或位数的数据进行排序,但是需要较大的空间,并且对数据的分布有一定的要求。

实际应用中,一般情况下,即输入数据是随机的,快速、归并、希尔、堆排序的效果最好。其中快速排序最快,堆排序最省空间。如果数据是基本有序的,插入和冒泡排序效果最好O(n)。其它排序法也都有一定的适用场景,要根据数据分布、类型、规模等综合考虑采用哪种排序算法。

到此排序部分就全部写完了,基本能保证测试数据能跑通,因为很多排序算法有不同实现方法,笔者基本上按照动图中演示的过程写的代码(快排的动图是取中间元素划界,但笔者的实现是最后元素划界,基数排序也不能保证–这就不是我写的)。写成这一系列文章也不容易,万多字,仅代码也不太可能一天写完并测试,所以分了几天才完成。

原创文章,未经许可,严禁转载

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

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

相关文章

本地gradle在idea中的配置

因为公司在用的gradle构建项目&#xff0c;由于学安卓时候把gradle搞的四不像&#xff0c;所以重新配置了gradle在此记录一下 文章目录 安装gradel官网下载解压init.d里面创建init.gradle文件父目录创建gradleRepository配置环境变量测试是否配置成功 idea配置gradle重新构建项…

深度学习笔记之Seq2seq(二)基于Seq2seq注意力机制的动机

深度学习笔记之Seq2seq——基于Seq2seq注意力机制的动机 引言回顾&#xff1a;基于机器翻译任务的 Seq2seq \text{Seq2seq} Seq2seq网络结构注意力机制的动机循环神经网络作为 Encoder \text{Encoder} Encoder产生 Context \text{Context} Context向量的缺陷注意力机制处理上述…

chatgpt赋能python:Python怎么从列表里随机抽取?

Python怎么从列表里随机抽取&#xff1f; 在编程中&#xff0c;我们常常需要从一个列表里面随机抽取一个元素来进行一些操作&#xff0c;比如说在一个游戏中随机抽取一个怪物来进行战斗。Python提供了一个内置模块——random模块&#xff0c;用于生成随机数。这个模块可以帮助…

(数组) 1207. 独一无二的出现次数 ——【Leetcode每日一题】

❓1207. 独一无二的出现次数 难度&#xff1a;简单 给你一个整数数组 arr&#xff0c;请你帮忙统计数组中每个数的出现次数。 如果每个数的出现次数都是独一无二的&#xff0c;就返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;arr [1,2,2,1,…

做一个比较有意思的条目选择动画 css

做一个比较有意思的条目选择动画 css 效果 如何实现 原理就是将母元素设置成 relative 然后在四边放四个 absolute 的色块。 初始状态是opacity 为 0 的&#xff0c;当母元素 hover 的时候&#xff0c;将四个边角色块设置 opacity: 1 并偏移指定量。 html <div class&qu…

English Learning - L3 作业打卡 Lesson5 Day32 2023.6.5 周一

English Learning - L3 作业打卡 Lesson5 Day32 2023.6.5 周一 引言&#x1f349;句1: What do you read when you are travelling by train or bus?成分划分弱读爆破语调 &#x1f349;句2: What are other passengers reading?成分划分弱读连读语调 &#x1f349;句3: Perh…

软考A计划-系统架构师-官方考试指定教程-(15/15)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

规则引擎--QLExpress:普通表达式的运行

目录 QLExpress普通表达式执行解析并转化为ExpressNode语法解析,得到如下的语法树根据 ExpressNode 树生成指令树执行指令树得到结果InstructionConstData 的指令执行InstructionOperator的指令执行 最后得到结果 再看一个in表达式设置参数的执行 QLExpress github: https://g…

【matlab】matlab算法封装成工具包提供给程序调用

说明&#xff1a; 1、非进程通讯协议&#xff0c;无需在电脑上安装完整版的matlab开发环境。 2、本项目以C#为案例&#xff0c;调用的语言不限&#xff0c;操作流程基本相同。 一、准备工作 1、安装MATLABWebAppServerSetup集成开发环境 2、安装Visual stdio 2017集成开发环…

Openharmony添加编译自己应用

介绍一下Openharmony如何在庞大的编译构建系统中&#xff0c;增添自己想编译的内容。不定期更新~&#x1f438; gn官方文档&#xff1a; https://gn.googlesource.com/gn//main/docs/quick_start.md https://gn.googlesource.com/gn//master/docs/reference.md openharmony官…

Salesforce退出市场后类似的CRM系统有哪些

Salesforce退出中国市场后&#xff0c;对很多使用Salesforce的国内企业来说是一个不小的打击。他们需要寻找与Salesforce功能相当、具有良好口碑的CRM客户管理系统来替代。本文就为大家推荐五款类似Salesforce的CRM系统。 1、Zoho CRM Zoho CRM是一款SaaS云端CRM系统&#xf…

005: vue中el-upload 组件添加token的方法

第005个 查看专栏目录: 按照VUE知识点 ------ 按照element UI知识点 echarts&#xff0c;openlayers&#xff0c;cesium&#xff0c;leaflet&#xff0c;mapbox&#xff0c;d3&#xff0c;canvas 免费交流社区 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏…

谈谈嵌入式开发中签名校验和加解密作用的理解

1、前言 本博文不是讲解可信加签和固件加密的具体原理&#xff0c;而是谈谈实际嵌入式开发中&#xff0c;可信加签和固件加密的应用场景&#xff0c;可以帮助从事嵌入式开发的人员快速理解加签和加密的作用。 2、嵌入式开发中可信加签和固件加密介绍 (1)各家公司都有自己的可信…

操作受限的线性表——栈

本文主要内容&#xff1a;本文主要讲解栈的基本概念、基本操作和栈的顺序、链式实现。 目录 栈一、栈的基本概念1、基本概念2、基本操作 二、栈的顺序存储结构1、顺序栈的实现2、顺序栈的基本运算1&#xff09;初始化2&#xff09;判栈空3&#xff09;进栈4&#xff09;出栈5&a…

【环境配置】C/C++第三方库管理工具vcpkg安装和使用

一&#xff0c;vcpkg简介 vcpkg是微软公司开发的一个开源C包管理工具&#xff0c;它可以很方便的帮助您在 Windows、 Linux 和 MacOS 上下载&#xff0c;编译和安装C 第三方库。它具有自动解决依赖关系的能力&#xff0c;并且支持多种目标架构和平台。提供了超过1500个C库的预…

【Ubuntu系统内核更新与卸载】

【Ubuntu系统内核更新与卸载】 1. 前言2. 内核安装2.1 系统更新2.2 官网下载 3. 内核卸载3.1 需求分析3.2 卸载方法 1. 前言 我们在搭建环境时常常遇到内核版本不匹配的问题&#xff0c;需要我们安装新的内核版本&#xff1b;有时又会遇到在安装软件时报错boot空间已满无法安装…

Python爬取影评并进行情感分析和数据可视化

Python爬取影评并进行情感分析和数据可视化 文章目录 Python爬取影评并进行情感分析和数据可视化一、引言二、使用requestsBeautifulSoup进行影评的爬取1、分析界面元素2、编写代码 三、情感分析1、数据预处理2、情感分析3、数据可视化 一、引言 前几天出了《航海王&#xff1…

N - Cthulhu

第三次题组 [Cloned] - Virtual Judge (vjudge.net) 【题目描述】 一个具有 n 个顶点和 m 条边的无向图。现在&#xff0c;世界上最好的头脑即将确定这张图是否可以被视为克苏鲁。 为了简单起见&#xff0c;让我们假设克苏鲁从空间里看起来就像一个附有触手的球形身体。从形式…

sqlserver存储过程中使用临时表的问题

2023年6月6日08:52:15 因为最近接触的his系统一些存储过程做数据统计&#xff0c;一个存储过程就要使用1-3个临时表&#xff0c;这些存储过程是零几年的写得&#xff0c;和我们这个时代的写的存储过程习惯不太一样&#xff0c;就好奇为什么要使用这么多的临时表 临时表的基本概…

结构型设计模式05-组合模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 组合模式 1、组合模式介绍 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分…