八大排序算法(含时间复杂度、空间复杂度、算法稳定性)

news2024/11/26 5:47:10

文章目录

  • 八大排序算法(含时间复杂度、空间复杂度、算法稳定性)
    • 1、(直接)插入排序
      • 1.1、算法思想
      • 1.2、排序过程图解
      • 1.3、排序代码
    • 2、希尔排序
    • 3、冒泡排序
      • 3.1、算法思想
      • 3.2、排序过程图解
      • 3.3、排序代码
    • 4、(简单)选择排序
      • 4.1、算法思想
      • 4.2、排序过程图解
      • 4.3、排序代码
    • 5、堆排序
    • 6、快速排序
    • 7、归并排序
    • 8、计数排序
      • 8.1、算法思想
      • 8.2、排序过程图解
      • 8.3、排序代码

img

八大排序算法(含时间复杂度、空间复杂度、算法稳定性)

下列算法默认都是对数组进行升序

1、(直接)插入排序

1.1、算法思想

  • 插入排序是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

    插入排序的具体步骤如下:

    1. 从第一个元素开始,该元素可以认为已经被排序;
    2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
    3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
    4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
    5. 将新元素插入到该位置后;
    6. 重复步骤2~5。

img


1.2、排序过程图解

  • 从第一个元素开始,该元素可以认为已经被排序,取出下一个元素并记录到临时变量tmp中,在已经排序的元素序列中从后向前扫描(end--),如果该元素(已排序)大于新元素,将该元素移到下一位置,如果该元素小于等于新元素,则直接在这个元素的后面把新元素放进来。

    • 这里仅演示部分过程,其他过程自行考虑(和上述过程类似)。

1.3、排序代码

  • end指向当前要插入元素的前一个位置end+1指向当前要插入元素的位置),tmp保存当前要插入的元素,在已经排序的元素序列中从后向前扫描,找到比新元素小的元素的时候(因为有序,这个位置前面的元素比这个元素更小),直接把新元素插入到这个位置的后面。

    //插入排序
    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];
                } else {
                    break;
                }
                --end;
            }
            arr[end + 1] = tmp;
        }
    }
    
  • 时间复杂度计算:

    • 最坏时间复杂度:数组元素原本是降序,现要求使其升序。那么每个元素需要移动或者比较的次数为:

      • 第一个元素:0
      • 第二个元素:1
      • 第三个元素:2
      • 第n个元素:n-1

      总次数:0+1+2+3+...+n-1 = n*(n-1)/2

      所以最坏时间复杂度为:O(n^2)

    • 最好时间复杂度:考虑数组原本是升序,那么所有元素需要移动或者比较的总次数为:0+1+1+...+1 = n-1。所以最好时间复杂度为O(n)

    • 平均时间复杂度:O(n^2) ----> 算法不太行

  • 空间复杂度计算:由于没有开辟额外空间来辅助数组排序,故空间复杂度为O(1)

  • 算法稳定性稳定,因为对于值相同的元素,后插入的时候不会插到相同元素的前面(tmp >= arr[end]break,即不插入)。


2、希尔排序

希尔排序详解


3、冒泡排序

3.1、算法思想

  • 冒泡排序是通过对相邻元素的比较和位置交换,使得每次遍历都可以得到剩余元素中的最大值,将其放入有序序列中最终的位置,然后下一趟排序的时候就不用去比较这个已经确定了的元素。在冒泡排序中,会依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就如同水底下的气泡一样逐渐向上冒。

3.2、排序过程图解

  • 每趟排序可以把一个元素”冒“到最终位置上,下一趟排序就可以少排序一个元素。

    • 这里仅演示部分过程,其他过程自行考虑(和上述过程类似)。

3.3、排序代码

  • 指针i控制每趟需要少排序的元素个数(即已经有i个元素已经在最终位置上),指针j用来比较相邻元素的大小,若相邻元素是降序,则交换这两个元素。

  • 这里定义了一个flag,用来标记每趟排序是否有交换,如果有交换,就需要继续下一趟排序,没有交换则说明数组已经有序,那么就不用继续下一趟排序!

    void Swap(int *a, int *b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    
    //冒泡排序
    void BubbleSort(int *arr, int n) {
        for (int i = 0; i < n; ++i) {
            int flag = 0;
            for (int j = 0; j < n - 1 - i; ++j) {
                if (arr[j + 1] < arr[j]) {
                    flag = 1;
                    Swap(&arr[j], &arr[j + 1]);
                }
            }
            if (flag == 0) {
                break;
            }
        }
    }
    
  • 时间复杂度计算:

    • 最坏时间复杂度:考虑数组原本是降序,现在要求其升序。那么每个元素需要移动或者比较的次数为:

      • 第一趟排序:n-1
      • 第二趟排序:n-2
      • 第三趟排序:n-3
      • 第n趟排序:1

      总次数:n-1+n-2+n-3+...+1 = n*(n-1)/2

      所以最坏时间复杂度为:O(n^2)

    • 最好时间复杂度:考虑数组原本是升序。那么所有元素需要移动或者比较的次数为:

      若不使用flag:比较次数为n-1+n-2+n-3+...+1 = n*(n-1)/2次。

      使用flag:比较次数为n-1次。

    • 平均时间复杂度:O(n^2) —> 算法不太行

  • 空间复杂度计算:由于没有开辟额外空间来辅助数组排序,故空间复杂度为O(1)

  • 算法稳定性稳定,因为对于值相同的元素,每一趟排序的时候不会交换(arr[j + 1] < arr[j]才交换)。


4、(简单)选择排序

4.1、算法思想

  • 选择排序是一种简单直观的排序算法。它的工作原理如下:(优化后的选择排序–>每次都能确定当前未排序序列的最小元素和最大元素的最终位置)
    1. 在未排序序列中找到最小元素和最大元素,最小元素存放到排序序列的起始位置,最大元素存放到排序序列的末尾位置。
    2. 再从剩余未排序元素中继续寻找最小元素和最大元素,然后最小元素放到前面已排序序列的末尾最大元素放到后面已排序序列的前面
    3. 以此类推,直到所有元素均排序完毕。
  • 这里动画排序是每次选出一个最小值。(我们讲的算法更优哈哈)

4.2、排序过程图解

  • 在未排序序列中找到最小元素和最大元素,最小元素存放到排序序列的起始位置,最大元素存放到排序序列的末尾位置。

  • 再从剩余未排序元素中继续寻找最小元素和最大元素,然后最小元素放到前面已排序序列的末尾最大元素放到后面已排序序列的前面

    • 这里仅演示部分过程,其他过程自行考虑(和上述过程类似)。
  • 以此类推,直到所有元素均排序完毕。


4.3、排序代码

  • 使用minimaxi分别记录当前未排序的最小值下标和最大值下标在未排序的序列中找出最小值和最大值,然后分别交换到当前未排序的起始位置和末尾位置。需要注意的是如果当前未排序的序列中,最大值刚好在未排序序列的起始位置,那么就需要记录好这个最大值与当前未排序的序列中的最小值交换后的位置,不记录的话,那么当前maxi指向的值不一定是最大值!

    //选择排序
    void SelectSort(int *arr, int n) {
        int mini = 0;
        int maxi = 0;
        int start = 0;
        int end = n - 1;
        while (start < end) {
            for (int i = start + 1; i <= end; ++i) {
                if (arr[i] > arr[maxi]) {
                    maxi = i;
                }
                if (arr[i] < arr[mini]) {
                    mini = i;
                }
            }
            Swap(&arr[mini], &arr[start]);
            //注意此时如果start刚好是最大值的话,就会把最大值换走了,也就是本来最大值在 0 位置,交换后换到其他位置了,所以判断一下
            if (start == maxi) {
                maxi = mini;//找到最大值的下标
            }
            Swap(&arr[maxi], &arr[end]);
            //向中间靠拢
            ++start;
            --end;
        }
    }
    
  • 时间复杂度计算:对于选择排序排序来说,没有什么最坏时间复杂度和最好时间复杂度,因为不管原数组起始是升序还是降序,元素之间的比较次数都是一样的:

    • 确定了2个元素的最终位置:n-1
    • 确定了4个元素的最终位置:n-1+n-3
    • 确定了6个元素的最终位置:n-1+n-3+n-5
    • 确定了n个元素的最终位置:n+n-3+n-5+...+1 = n*(n+1)/4 <---大约,所以时间复杂度为O(n^2)
  • 空间复杂度计算:由于没有开辟额外空间来辅助数组排序,故空间复杂度为O(1)

  • 算法稳定性不稳定,考虑序列(1,2,2),排序后序列为(1,2,2),我们发现2的相对位置发生了变化,所以是不稳定的排序算法。


5、堆排序

堆排序详解


6、快速排序

快速排序递归方法和非递归方法详解


7、归并排序

快速排序递归方法和非递归方法详解


8、计数排序

8.1、算法思想

  • 计数排序就是使用一个临时数组来记录这个原数组的元素对应这个临时数组下标出现的次数,然后再对这个临时数组0开始往后按下标出现的次数遍历。

  • 优化:对于原数组最小值较大的情况,我们可以使用对这个临时数组进行==重定位==。

    • 重定位:相当于计算机组成原理里面的将逻辑地址转化为物理地址的过程,比如序列110,110,111,120,125,122,115,118,112,118,其实它的范围就是在110~125,区间长度为16,如果我们按照这个序列的最大值来建立数组,那么需要长度为126的数组,但是这个数组的前110个空间都是0,也就是并没有用上,浪费了。但是如果创建一个长度为16的数组,下标为0~15(原数组每个元素减110,这个110是这个原数组的最小值),是不是就可以匹配这个序列的范围了呢?

      那么问题是之后遍历这个临时数组,只能得到0~15的下标,并不是我们要的110~125!其实,在遍历这个临时数组的时候,可以继续使用重定位,把这个0~15的下标重定位到110~125(每个下标都加110,这个110是这个原数组的最小值)!

8.2、排序过程图解

  • 先找到原数组的最大值和最小值,然后就可以确定临时数组的长度,然后初始化这个临时数组(全0)。

  • 然后依次遍历原数组,根据重定位,将原数组的元素减去最小值去对应临时数组的下标,并对这个下标里的元素+1

  • 遍历这个临时数组,对每个下标进行遍历,按下标对应的元素值看需要对此下标遍历几次(需要重定位回去—加上原数组的最小值)。


8.3、排序代码

  • minmax记录原数组的最小值和最大值,确定临时数组的长度(max-min+1),然后对临时数组count进行初始化,接下来就是把原数组里的元素重定位为临时数组的下标(元素值减原数组的最小值),并对此下标对应的元素+1,一直到遍历完原数组。

  • 遍历这个临时数组count,对每个下标进行遍历,按下标对应的元素值看需要对此下标遍历几次(需要重定位回去—加上原数组的最小值)。

  • 注意:这里不能找最大最小值的下标,因为在重定位回去的时候arr[j++]在变,也就是最小值下标不一定对应到最小值了!

    //计数排序
    void CountSort(int *arr, int n) {
        //先找出数组的最大最小值
        int max = arr[0];
        int min = arr[0];
        for (int i = 1; i < n; ++i) {
            if (arr[i] > max) {
                max = arr[i];
            }
            if (arr[i] < min) {
                min = arr[i];
            }
        }
        //节省空间,需要对元素重定位
        int capacity = max - min + 1;//元素大小区间
        //记录每个元素的出现次数
        int *count = (int *) malloc(sizeof(int) * capacity);
        if (count == NULL) {
            perror("malloc error");
            exit(-1);
        }
        memset(count, 0, sizeof(int) * capacity);
        for (int i = 0; i < n; ++i) {
            count[arr[i] - min]++;
        }
        int j = 0;
        for (int i = 0; i < capacity; ++i) {
            while (count[i]--) {
                arr[j++] = i + min;
            }
        }
        free(count);
    }
    
  • 时间复杂度计算:这里找最大值最小值花费时间n,遍历临时数组花费时间k(临时数组长度),所以时间复杂度为O(n+k)

  • 空间复杂度计算:使用了临时数组(临时数组长度为k),所以空间复杂度为O(k)

  • 算法稳定性稳定,因为它是利用一个数据的索引来记录元素出现的次数,而这个数组的索引就是元素的数值。当计数排序完成后,具有相同数值的元素在数组中的位置也相同,因此它们的顺序保持不变。


八大排序算法整体的时间复杂度、空间复杂度、算法稳定性等看如下表格:

排序算法平均时间复杂度最好情况最坏情况空间复杂度排序方式稳定性
(直接)插入排序O(n^2)O(n)O(n^2)O(1)内部排序稳定
希尔排序O(n^1.3)O(n^1.3)O(n^1.3)O(1)内部排序不稳定
冒泡排序O(n^2)O(n)O(n^2)O(1)内部排序稳定
(简单)选择排序O(n^2)O(n^2)O(n^2)O(1)内部排序不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)内部排序不稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn)内部排序不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)外部排序稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)外部排序稳定

OKOK,八大排序算法就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

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

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

相关文章

Web应用防火墙的性能优化技术

Web应用防火墙&#xff08;WAF&#xff09;是企业网络安全的重要屏障&#xff0c;其性能直接影响到网络服务的质量和安全。本文详细探讨了WAF性能优化的几种技术&#xff0c;旨在为网络安全专业人员提供实用的参考。 规则优化 1.1 精简规则集 规则评估&#xff1a;定期评估规…

深入了解Spring Boot Actuator

文章目录 引言什么是ActuatorActuator的底层技术和原理端点自动配置端点请求处理端点数据提供端点数据暴露 如何使用Actuator添加依赖访问端点自定义端点 实例演示结论 引言 Spring Boot Actuator是一个非常强大且广泛使用的模块&#xff0c;它为Spring Boot应用程序提供了一套…

数据库:Hive转Presto(五)

此篇将所有代码都补充完了&#xff0c;之前发现有的代码写错了&#xff0c;以这篇为准&#xff0c;以下为完整代码&#xff0c;如果发现我有什么考虑不周的地方&#xff0c;可以评论提建议&#xff0c;感谢。代码是想哪写哪&#xff0c;可能比较繁琐&#xff0c;还需要优化。 …

Kafka SASL认证授权(五)ACL源码解析

Kafka SASL认证授权(五)ACL源码解析。 官网地址:https://kafka.apache.org/ 一、ACL检查流程解析 一起看一下kafka server的启动与监听流程: Kafka -> KafkaServer -> SocketServer、KafkaRequestHandler 其中KafkaServer做相关的初始化,包括SocketServer 与 han…

实体解析实施的复杂性

实体的艺术表现斯特凡伯克纳 一、说明 实体解析是确定数据集中的两条或多条记录是否引用同一现实世界实体&#xff08;通常是个人或公司&#xff09;的过程。乍一看&#xff0c;实体分辨率可能看起来像一个相对简单的任务&#xff1a;例如&#xff0c;给定一张人物的两张照片&a…

AOMEI PXE Boot Free

两台电脑网线直连&#xff0c;不用设置固定IP&#xff0c;该软件包含DHCP。 名称: 3H3AOMEIPXEBootFree.rar 大小: 13068734 字节 (12 MiB) SHA1: 1e606c8c1ee3badf8af9a87f61fdf2e332b773e6 名称: PXEBoot.exe 大小: 13124928 字节 (12 MiB) SHA1: 95286ac18e9b81c2a68412c40…

尿检设备“智能之眼”:维视智造推出MV-MC 系列医疗专用相机

​ 尿液分析是临床检验的基础常规项目&#xff0c;随着医疗设备的不断发展&#xff0c;尿液分析相关仪器的国产化和自动化程度也进一步提升。2022 年国内尿液分析市场的规模约为 28 亿元&#xff0c;激烈的竞争推动了尿检仪器自动化、智能化升级&#xff0c;在仪器中加入机器视…

布朗大学发现GPT-4存在新问题,可通过非常见语言绕过限制

&#x1f989; AI新闻 &#x1f680; 布朗大学发现GPT-4存在新漏洞&#xff0c;可通过非常见语言绕过限制 摘要&#xff1a;布朗大学计算机科学研究人员发现了OpenAI的GPT-4存在新漏洞&#xff0c;利用不太常见的语言如祖鲁语和盖尔语可以绕过各种限制。研究人员测试了GPT-4对…

如何做好数据分析中的数据可视化?

数据可视化在数据分析中扮演着重要的角色&#xff0c;它帮助我们更好地理解和传达数据的特征、趋势和规律。以下是关于如何做好数据分析中的数据可视化的详细介绍。 一、准备工作 1. 理解数据 在进行数据可视化之前&#xff0c;首先要对数据有一个清晰的理解。了解数据的来源…

【大数据】Hadoop MapReduce与Hadoop YARN(学习笔记)

一、Hadoop MapReduce介绍 1、设计构思 1&#xff09;如何对付大数据处理场景 对相互间不具有计算依赖关系的大数据计算任务&#xff0c;实现并行最自然的办法就是采取MapReduce分而治之的策略。 不可拆分的计算任务或相互间有依赖关系的数据无法进行并行计算&#xff01; …

LiveMedia视频中间件如何与第三方系统实现事件录像关联

一、平台简介 LiveMedia视频中间件是支持部署到本地服务器或者云服务器的纯软件服务&#xff0c;也提供服务器、GPU一体机全包服务&#xff0c;提供视频设备管理、无插件、跨平台的实时视频、历史回放、语音对讲、设备控制等基础功能&#xff0c;支持视频协议有海康、大华私有协…

Qt事件系统 day7

Qt事件系统 day7 事件系统 在Qt中&#xff0c;事件是派生自抽象QEvent类的对象&#xff0c;它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理&#xff0c;但它们与小部件尤其相关。Qt程序需要在main()函数创建一个…

【iOS】Mac M1安装iPhone及iPad的app时设置问题

【iOS】Mac M1安装iPhone及iPad的app时设置问题 简介一&#xff0c;设置问题二&#xff0c;适配问题 简介 由于 苹果M1芯片的Mac可用安装iPhone以及iPad应用&#xff0c;因为开发者并没有适配Mac&#xff0c;因此产生了很多奇怪问题&#xff0c;这里总结归纳Mac M1安装iPhone和…

C++之委托构造函数实例(二百四十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

学信息系统项目管理师第4版系列26_项目绩效域(下)

1. 项目工作绩效域 1.1. 涉及项目工作相关的活动和职能 1.2. 预期目标 1.2.1. 高效且有效的项目绩效 1.2.2. 适合项目和环境的项目过程 1.2.3. 干系人适当的沟通和参与 1.2.4. 对实物资源进行了有效管理 1.2.5. 对采购进行了有效管理 1.2.6. 有效处理了变更 1.2.7. 通…

第八章:网络设备文件管理)

网络设备文件管理 一、网络设备的文件系统1. 网络设备的存储方式2. 网络设备的操作 二、文件管理1. 配置文件的操作2. 配置文件的显示与维护![ ](https://img-blog.csdnimg.cn/a4f36f4e8f50422eb76be4ea7794bad9.png) 三、网络连通测试四、系统调试 一、网络设备的文件系统 1. …

2023年09月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 Python编程&#xff08;1~6级&#xff09;全部真题・点这里 第1题&#xff1a;生日相同 在一个有180人的大班级中&#xff0c;存在两个人生日相同的概率非常大&#xff0c;现给出每个学生的名字&#xff0c;出生月日。试…

idea 启动出现 Failed to create JVM JVM Path

错误 idea 启动出现如下图情况 Error launching IDEA If you already a 64-bit JDK installed, define a JAVA_HOME variable in Computer > System Properties> System Settings > Environment Vanables. Failed to create JVM. JVM Path: D:\Program Files\JetB…

Redis微服务架构

Redis微服务架构 缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c;缓存层和存储层都不会命中&#xff0c;通常出于容错的考虑&#xff0c;如果从存储层查不到数据则不写入缓层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c;失去…

真正牛的项目经理,都做到了这几点

大家好&#xff0c;我是老原。 不管你是在大厂还是小厂&#xff0c;都可以看到这么一群人&#xff0c;他们具备&#xff1a;优秀的产品规划、持续的商务拓展、准时交付项目/产品。 然后大部分人是&#xff1a;一般的产品规划、眼睁睁看着客户流失、项目/产品交付不断推迟。 …