十大排序算法(上)直接插入排序、希尔排序、直接选择排序、堆排序

news2024/11/27 0:46:51

🌈目录

  • 1. 排序的概念
  • 2. 常见的排序算法
  • 3. 排序算法的实现
    • 3.1 插入排序
      • 3.1.1 直接插入排序
      • 3.1.2 希尔排序(缩小增量排序)
    • 3.2 选择排序
      • 3.2.1 基本思想
      • 3.2.2 直接选择排序
      • 3.2.3 堆排序

1. 排序的概念

排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假设在待排序的序列中,存在多个具有相同内容的元素,如果经过排序,这些元素的相对位置并不发生改变,则称这种排序算法是稳定的。

稳定的排序可以变成不稳定的,但是不稳定的排序算法是不可能变成稳定的。

例如:

排序前:4a 6 5 9 3 4b 7 2

排序后:2 3 4a 4b 5 6 7 9 ==》稳定

排序后:2 3 4b 4a 5 6 7 9 ==》不稳定

稳定性存在的意义是什么?

比如说:小明用半小时交卷得了一百分,小刚用一小时交卷得了一百分,最终以分数排名的时候,第一名一定是小明,不可能是小刚,因为小明比小刚先交卷。

稳定性好的排序就会把小明放在第一名,而稳定性差的排序可能会把小刚放在第一名,对小明不公平。

这样稳定性的意义就体现出来了。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求在内外存之间移动数据的排序。

2. 常见的排序算法

在这里插入图片描述

3. 排序算法的实现

3.1 插入排序

插入排序的中心思想就是:将待排序的元素按照自身大小逐个插入到已经排好序的有序序列中,直到所有的元素插完为止。

3.1.1 直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

public static void insertSort(int[] array) {
    for (int i = 1; i < array.length; i++) {
        int n = array[i];//当前待排序元素
        int j = i - 1;
        for (; j >= 0; j--) {
            //待排序的元素与前面排好序的元素进行比较
            if(n < array[j]) {
                //当前待排序元素<排好序的元素
                array[j + 1] = array[j];
                //排好序的元素后移
            } else {
                //当前待排序元素>=排好序的元素,证明此时待排序元素找到了自己的位置,中断比较
                break;
            }
        }
        array[j + 1] = n;//将待排序的元素放在自己的位置。
        //为什么是将待排序的元素赋值给array[j + 1]呢?
        //因为array[j + 1] > n > array[j] ,array[j + 1] 的值已经赋给后一个元素了,所以将待排序的元素赋值给array[j + 1]
    }
}

测试10_0000个数据排序所用时间(单位:ms)

在这里插入图片描述

特点

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高。
  2. 时间复杂度:最坏的情况下(逆序)O(N2), 最好的情况下(正序)O(N);
  3. 数据越有序的时候,排序越快
  4. 空间复杂度:O(1)
  5. 稳定性:稳定

3.1.2 希尔排序(缩小增量排序)

希尔排序的中心思想是:

先选定一个整数,把待排序元素分成多个组,将所有距离为n的元素分到一组内,然后对每一组内的元素进行排序,然后取n的一半或者其他小于n的整数,重复上述工作。当n = 1时,所有的元素在统一组内排好序。

简单来说,就分为两步:

  1. 分组->缩小增量

    这里的分组并不是将连续的元素分在一组内,而是跳跃式分组,这样可以尽量把大的数据放到后面,把小的数据放到前面

    在这里插入图片描述

  2. 组内进行插入排序

    组内的每一次排序都是插入排序

gap的取法有很多种:可以是gap = n / 2,gap = gap / 2;也可以是gap = gap / 3 + 1;还有人说都取奇数为好;也有人提出gap互质为好。但是无论哪一种都没有得到证明。

我用的是gap = n / 2,gap = gap / 2这种取法。

下面是大体步骤:

在这里插入图片描述

public static void shell(int[] arr, int gap) {
    for(int i = gap; i < arr.length; i++) {
        //这里为什么是i++而不是i+=gap的原因:
        //因为如果是i+=gap,i只会比较第一个组,因为第一个组比较完了之后,i > arr.length,不能进入循环了。
        //而如果是i++,那么i就会遍历到每一个组,每一个成员,进行插入排序。
        int n = arr[i];
        int j = i - gap;
        for(; j >= 0; j-=gap) {
            //这里j-=gap就能保证每个元素都在自己的组内进行比较
            if(arr[j] > n) {
                arr[j + gap] = arr[j];
            } else {
                break;
            }
        }
        arr[j + gap] = n;
    }
}
public static void shellSort(int[] arr) {
    int gap = arr.length;
    while(gap > 1) {
        gap /= 2;
        shell(arr, gap);
    }
}

在这里插入图片描述

特点

  1. 希尔排序是对直接插入排序的优化
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近于有序了,这样就会很快。
  3. 希尔排序的时间复杂度不好计算,因为他的运行时间依赖于gap的选择,导致很难去计算,是一个长期未解决的问题。在很多书中给出的时间复杂度都不固定。大约是在n1.25 到1.6*n1.25 范围内
  4. 不稳定

3.2 选择排序

3.2.1 基本思想

每一次从待排序的数据元素中选出最小(大)的一个元素,存放在序列的起始位置,直到全部待排序的元素排完。

3.2.2 直接选择排序

升序 :在元素arr[i] – arr[n - 1]中选出最小的元素,与arr[i]进行交换,i++,重复上述步骤,直到i遍历到最后一个元素为止。

public static void selectSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        int index = i;
        for (int j = i + 1; j < arr.length; j++) {
            if(arr[index] > arr[j]) {
                //挑选最小元素,记录位置
                index = j;
            }
        }
        swap(arr, i, index);
    }
}
private static void swap(int[] arr, int index1, int index2) {
    int n = arr[index1];
    arr[index1] = arr[index2];
    arr[index2] = n;
}

还有另一种做法,就是每次遍历都会找出一个最大值和最小值,进行两次交换。两种做法的时间复杂度是一样的。

public static void selectSort0(int[] arr) {
    int left = 0;
    int right = arr.length - 1;
    while (right > left) {
        int minIndex = left;
        int maxIndex = left;
        for(int i = left + 1; i <= right; i++) {
            if(arr[i] < arr[minIndex]) {
                minIndex = i;
            }
            if (arr[i] > arr[maxIndex]) {
                maxIndex = i;
            }
        }
        swap(arr, minIndex, left);
        //这里要特别注意一点:
        //如果left这个下标的值是最大值,left和minIndex换了之后,最大值的下标就不是maxIndex了,最大值就被换到了minIndex了,所以这里要判断一下。
        if(maxIndex == left) {
            maxIndex = minIndex;
        }
        swap(arr, maxIndex, right);

        left++;
        right--;
    }
}

特点:

  1. 直接选择排序的思路好理解,但是效率比较低,不常用
  2. 时间复杂度:O(N2),数组本身的序列对选择排序的时间复杂度没有影响。
  3. 空间复杂度:O(1)
  4. 不稳定

在这里插入图片描述

可能会有人奇怪,逆序情况下,直接插入排序和选择排序的时间复杂度都是O(N2),为什么时间相差很多呢?

因为插入排序越排序越有序,越有序就越快。

注意:这里展现的时间只是感受一下排序之间的不同,仅供参考,每次运行的结果都不一样,并且数据量比较小,不能说明排序的时间复杂度!

3.2.3 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆
  • 升序:建大堆
  • 降序:建小堆
  1. 利用堆删除思想来进行排序
    按升序来举例,升序要建大堆,为什么呢?因为大根堆可以保证堆顶元素是最大的,然后可以将堆顶元素和最后一个元素进行交换,这样就能确保最大的元素在最后面,然后再对0下标这棵树向下调整,以此类推,就可以得到一组有序的数据。
    如图所示:
    在这里插入图片描述
    建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
public static void heapSort(int[] arr) {
    createBigHeat(arr);
    int end = arr.length - 1;
    while (end > 0) {
        //让堆顶元素与最后一个元素进行交换,因为堆顶元素是最大的元素,将它放在最后一个,就让最大的元素有序了
        swap(arr, 0, end);
        //只有0下标这棵树不符合大根堆,所以只需对0下标这一颗树进行向下调整就行
        siftDown(arr, 0, end);
        end--;
    }
}
private static void createBigHeat(int[] arr) {
    for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
        siftDown(arr, i, arr.length);
    }
}
private static void siftDown(int[] arr, int parent, int len) {
    int child = parent * 2 + 1;
    while(child < len) {
        if(child + 1 < len && arr[child] < arr[child + 1]) {
            child++;
        }
        if(arr[child] > arr[parent]) {
            swap(arr, child, parent);
            parent = child;
            child = parent * 2 + 1;
        } else {
            break;
        }
    }
}

特点

  1. 因为使用了堆,比直接选择排序效率高很多。
  2. 时间复杂度:O(N*logN),数组的初始顺序对堆排序的复杂度没有影响。
  3. 空间复杂度:O(1)
  4. 不稳定

在这里插入图片描述
更多关于堆的文章可以查看我的另一篇文章哦 -> 点击这里
这篇文章先到这里,有什么疑问的,欢迎到评论区留言,我们下一篇文章再见~😊
下篇预告:十大排序算法(中):冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归)

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

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

相关文章

阿里通义千问_VS_讯飞星火

今天终于获得阿里通义千问大模型体验授权&#xff0c;第一时间来测试一下效果&#xff0c;使用申请手机号登录&#xff08;地址&#xff1a;https://tongyi.aliyun.com&#xff09;后&#xff0c;需要同意通义千问大模型体验规则&#xff0c;如下图所示&#xff1a; 同意之后就…

【C++初阶】类与对象(中)之运算符重载 + 赋值运算符重载

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

CPU性能优化:Cache

CPU性能提升&#xff1a;Cache机制 随着半导体工艺和芯片设计技术的发展&#xff0c;CPU的工作频率也越来越高&#xff0c;和CPU进行频繁的数据交换的内存的运行速度却没有相应的提升&#xff0c;于是两者之间产生了带宽问题。进而影响计算机系统的整体性能。CPU执行一条指令需…

C++/PTA 至多删三个字符

至多删三个字符 题目要求解题思路代码总结 题目要求 给定一个全部由小写英文字母组成的字符串&#xff0c;允许你至多删掉其中 3 个字符&#xff0c;结果可能有多少种不同的字符串&#xff1f; 输入格式&#xff1a; 输入在一行中给出全部由小写英文字母组成的、长度在区间 […

关于摆摊气球的调研-网红气球

本章主要介绍一下最近网红气球&#xff1a; 最近看到很多摆摊的抖音视频&#xff0c;都在说卖气球很好&#xff0c;成本低&#xff0c;收益高&#xff0c;所以调研了一下&#xff0c;网红气球分好几种&#xff1a; a,飘空气球&#xff1b; b.手持网红气球 c.青蛙 首先介绍飘空…

文件上传,内容逻辑数组绕过(22)

uploadd 第十三关 这一关告诉我们的&#xff0c;有一些上传漏洞需要配合这个文件包含和加解密。 这个先在一个图片源码里面写入php后门的脚本代码 这里也可以手工注入到图片的源码里面来&#xff0c;手工注入&#xff0c;如果采用16进制打开这个图片&#xff0c;这个图片在…

okhttp篇4:RetryAndFollowUpInterceptor

在上一篇 okhttp篇3&#xff1a;RealCall_yolan6824的博客-CSDN博客 中讲到RealCall无论是在execute还是enqueue方法中&#xff0c;都是通过getResponseWithInterceptorChain方法获取Request对应的Response的。而getResponseWithInterceptorChain这个方法&#xff0c;又是通过…

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程]

基于PyQt5的图形化界面开发——Windows内存资源监视助手[附带编译exe教程] 0. 前言1. 资源信息获取函数——monitor.py2. UI界面——listen.py3. main.py4. 运行效果5. 编译 exe 程序6. 其他PyQt文章 0. 前言 利用 PyQt5 开发一个 windows 的资源监视助手&#xff0c;在使用虚…

【vimsolo】让vim看起来像VSCode:颜色主题和状态栏的配置

文章目录 1. 目的2. 理念&#xff1a; vimsolo3. vimrc: 配置颜色4. vimrc: 配置状态栏5. 拷贝颜色主题和.vimrc: python安装脚本 1. 目的 习惯了 VSCode 默认的配色&#xff1a;黑色主题&#xff0c;蓝色状态栏。偶尔使用 Vim 时想让 vim 伪装的像 VSCode&#xff0c;不考虑花…

Web 测试和 App 测试重点总结

单纯从功能测试的层面上来讲的话&#xff0c;App 测试、Web 测试在流程和功能测试上是没有区别的&#xff0c;但由于系统结构方面存在差异&#xff08;web 项目&#xff0c;b/s 架构&#xff1b;app 项目&#xff0c;c/s 结构&#xff09;在测试中还是有不同的侧重点内容&#…

ZED使用指南(八)Depth Sensing

ZED立体相机再现了人类双目视觉的工作方式。通过比较左眼和右眼看到的两种视图&#xff0c;不仅可以推断深度&#xff0c;还可以推断空间中的3D运动。 ZED立体相机可以捕捉到场景的高分辨率3D视频&#xff0c;通过比较左右图像之间的像素位移可以估计深度和运动。 深度感知 …

CTFHub-ctfhub-Git泄露-Log

CTFHub-ctfhub-Git泄露-Log 当前大量开发人员使用git进行版本控制&#xff0c;对站点自动部署。如果配置不当,可能会将.git文件夹直接部署到线上环境。这就引起了git泄露漏洞。请尝试使用BugScanTeam的GitHack完成本题 1、dirsearch扫描 github上下载dirsearch-master 命令F…

SpringMVC第二阶段:@RequestMapping注解详解

RequestMapping注解详解 RequestMapping是给个方法配置一个访问地址。就比如web学习的Servlet程序&#xff0c;在web.xml中配置了访问地址之后&#xff0c;它们之间就有一个访问映射关系。 1、value属性 value 属性用于配置方法对应的访问地址. /*** RequestMapping 可以配…

JavaScript实现背景图像切换3D动画效果

&#x1f431; 个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &am…

Flask全套知识点从入门到精通,学完可直接做项目

目录 Flask入门 运行方式 URL与函数的映射(动态路由) PostMan的使用 查询参数的获取 上传文件 其它参数 url_for 函数 响应-重定向 响应-响应内容 响应-自定义响应 Flask模板 模板介绍 模板的使用 模板-传参 模板使用url_for函数 过滤器介绍 Jinja模板自带过滤器 流程…

DTFT和DFT有何区别?一文为你讲解清楚

很多人在开始学习数字信号处理的时候&#xff0c;对于各种傅里叶变换特别是离散傅里叶变化的概念及作用完全不清楚&#xff0c;IC修真院在网上整理了关于DTFT、DFT的各知识点。下面就来了解一下关于DTFT和DFT的区别吧。 DTFT&#xff0c; DFT 的区别是含义不同、性质不同、用途…

Elasticsearch集群搭建与相关知识点整理

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章参考网上的课程&#xff0c;介绍Elasticsearch集群的搭建&#xff0c;以及Elasticsearch集群相关知识点整理。 如果文章有什么需要改进的地方还请大佬不吝赐教&am…

C++刷题--选择题4

1, 在&#xff08;&#xff09;情况下适宜采用 inline 定义内联函数 A 函数体含有循环语句 B 函数体含有递归语句 C 函数代码少、频繁调用 D 函数代码多&#xff0c;不常调用 解析 C&#xff0c;以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方…

SpringSecurity实现角色权限控制(SpringBoot+SpringSecurity+JWT)

文章目录 一、项目介绍二、SpringSecurity简介SpringSecurity中的几个重要组件&#xff1a;1.SecurityContextHolder&#xff08;class&#xff09;2.SecurityContext&#xff08;Interface&#xff09;3.Authentication&#xff08;Interface&#xff09;4.AuthenticationMana…

c++项目环境搭建(VMware+linux+ubantu+vscode+cmake)

想运行一个c项目&#xff0c;但是环境怎么整呢&#xff1f;b站走起&#xff01;&#xff01;&#xff01; 本文需要的安装包 链接&#xff1a;https://pan.baidu.com/s/1XJbR2F1boQ-CqV8P71UOqw 提取码&#xff1a;swin 一、在虚拟机中安装ubantu 八分钟完成VMware和ubunt…