【算法】排序详解(快速排序,堆排序,归并排序,插入排序,希尔排序,选择排序,冒泡排序)

目录

排序的概念:

排序算法的实现:

插入排序:

希尔排序:

选择排序:

堆排序:

冒泡排序:

快速排序:

快速排序的基本框架:

1.Hoare法

2. 挖坑法

3.前后指针法

 快排的优化:

1. 三数取中法选key

2. 小区间使用插入排序

优化代码:

常见问题:

归并排序:

总结:

结语:


排序的概念:

排序:

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

稳定性:

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持 不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳 定的;否则称为不稳定的(稳定可以转换成不稳定的,不稳定不可以转换成稳定的)。

内部排序:

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

外部排序:

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

常见的排序算法:

直接插入排序,希尔排序,选择排序,堆排序,冒泡排序,快速排序,归并排序。

排序算法的实现:

说明:由于swap函数经常出现,为了使文章更加整洁,这里给出源码,下文直接调用不在说明。

 private static void swap(int[] array,int i,int j){int tmp = array[i];array[i] = array[j];array[j] = tmp;}

插入排序:

思路:在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。

动图演示如下:

代码实现如下:

 public static void insertSort(int[] array){for(int i = 1;i < array.length; i++){int j = i-1;int tmp = array[i];for(;j >= 0; j--){if(array[j] > tmp){array[j+1] = array[j];}else{break;}}array[j+1] = tmp;}}

结果演示:

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

希尔排序:

思路:希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达 =1时,所有记录在统一组内排好序。

动图演示:

代码实现如下:

在shellSort里面确定组的大小,在shell里面进行排序,通过计算确定gap的关系,间隔运行,一次通过。

 public static void shellSort(int[] array){int gap = array.length;while(gap > 1){gap /= 2;shell(array,gap);}}public static void shell(int[] array,int gap){for(int i = gap; i < array.length; i++){int j = 0;j = i-gap;int tmp = array[i];for(;j >= 0;j -= gap){if(array[j] > tmp){array[j+gap] = array[j];}else{break;}}array[j+gap] = tmp;}}

结果演示:

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很 快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排 序的时间复杂度都不固定。

4. 稳定性:不稳定

选择排序:

思路:

(1)在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素。

(2)若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。

(3)在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。

动图演示:

代码实现如下:

 //选择排序public static void selectSort(int[] array){for(int i = 0;i < array.length-1; i++){int minIndex = i;for(int j = i+1;j < array.length; j++){if(array[j] < array[minIndex]){minIndex = j;}}swap(array,i,minIndex);}}private static void swap(int[] array,int i,int j){int tmp = array[i];array[i] = array[j];array[j] = tmp;}

结果演示:

选择排序的特性总结 :

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:不稳定

堆排序:

思路:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。 

动图演示:

代码实现如下:

从小到大用大根堆

从大到小用小根堆

下面代码为大根堆

 public static void heapSort(int[] array){createBigHeap(array);int end = array.length-1;while(end > 0){swap(array,0,end);siftDown(array,0,end);end--;}}private static void createBigHeap(int[] array){for(int parent = (array.length - 1 -1)/2; parent >= 0; parent--){siftDown(array,parent,array.length);}}private static void siftDown(int[] array,int parent,int end){int child = parent*2+1;while(child < end) {if (child + 1 < end && array[child] < array[child + 1]) {child++;}if (array[child] > array[parent]) {swap(array, child, parent);parent = child;child = parent * 2 + 1;} else {break;}}}

 结果演示:

堆排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(1)

4. 稳定性:不稳定 

冒泡排序:

简单就不给思路了

动图演示:

 代码实现如下:

public static void bubbleSort(int[] array){for(int i = 0; i < array.length - 1; i++){boolean flg = false;for(int j = 0; j < array.length-1-i; j++){if(array[j] > array[j+1]){swap(array,j,j+1);flg = true;}}if(flg == false){return;}}}

 结果演示:

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1)

4. 稳定性:稳定 

快速排序:

思路:任取待排序元素序列中的某元 素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序的基本框架:

 //快排的框架public static void quickSort(int[] array,int left,int right){if(right <= left){return;}int div = partition(array,left,right);quickSort(array,left,div-1);quickSort(array,div+1,right);}

这是还没优化的。

partition可以得到left和right相遇的下标。

关于partition有三种求法分别是Hoare版,挖坑法,前后指针。

其中最常用的是挖坑法。

1.Hoare法

动图如下:

代码实现: 

 //Hoareprivate static int partition(int[] array,int left,int right){int i = left;int j = right;int pivot = array[left];while(j > i){while(j > i && array[j] >= pivot){j--;}while(j > i && array[i] <= pivot){i++;}swap(array,i,j);}swap(array,i,left);return i;}

2. 挖坑法

动图如下:

 代码实现: 

//挖坑法private static int partition(int[] array,int left,int right){int i = left;int j = right;int pivot = array[left];while(j > i){while(j > i && array[j] >= pivot){j--;}array[i] = array[j];while(j > i && array[i] <= pivot){i++;}array[j] = array[i];}array[i] = pivot;return i;}

3.前后指针法

代码如下:

 //前后指针法private static int partition(int[] array,int left,int right){int prev = left;int cur = left+1;while(cur <= right){if(array[cur] < array[left] && array[++prev] != array[cur]){swap(array,cur,prev);}cur++;}swap(array,prev,left);return prev;}

 快排的优化:

1. 三数取中法选key

使用该优化方法可以有效减少当数组有序时变成单叉树的时间复杂度。

基本思路:选取数组中第一个数,中间数和最后一个数比较大小,将其中中间值和最左边交换,这样可以使mid左后两边数组个数尽可能相等。

代码如下:

private static int middleNum(int[] array,int left,int right){int mid = left + ((right - left) >> 1);if(array[left] < array[right]){if(array[mid] < array[left]){return left;}else if(array[mid] < array[right]){return mid;}else{return right;}}else{if(array[mid] < array[right]){return right;}else if(array[mid] < array[left]){return mid;}else{return left;}}}
2. 小区间使用插入排序

思路:我们直到插入排序在数组接近有序时是非常快的,而快排最后在堆上调用的空间是非常大的,故在小区间上使用插入排序可以达到优化的效果。

代码如下:

//优化1if(right - left +1 <= 15){insertSort2(array,left,right);return;}private static void insertSort2(int[] array,int left,int right){if(left >= right){return;}for(int i = 1 + left;i <= right;i++){int tmp = array[i];//都定义可读性好int j = i-1;for(;j >= left;j--){if(array[j] > tmp){array[j+1] = array[j];}else{break;}}array[j+1] = tmp;}}
优化代码:

为节省文章长度,下面个代码在上面给出,下面我就不给总代码了(抱歉)。

public static void quickSort(int[] array,int left,int right){if(right <= left){return;}//优化1if(right - left +1 <= 15){insertSort2(array,left,right);return;}//优化2int index = middleNum(array,left,right);swap(array,index,left);int div = partition(array,left,right);quickSort(array,left,div-1);quickSort(array,div+1,right);}

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

常见问题:

1.在partition 方法中array[j] >= pivot 和 array[i] <= pivot中的等号能否去掉?

答:不能,因为当left和right下标的值等于pivot时会陷入死循环。

2.在partition 方法中能不能先从left开始遍历?

答:不能,因为这样最后和第一个数交换时会把比pivot大的数给到第一个(假设取得pivot取的都是第一个数)

归并排序:

思路:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使 子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

图片如下:

 代码实现:

先拆分后合并用递归实现拆分,merge实现合并。

//归并排序public static void mergeSort(int[] array,int left,int right){if(left >= right){return;}int mid = left + ((right - left) >> 1);mergeSort(array,left,mid);mergeSort(array,mid+1,right);merge(array,left,mid,right);}private static void merge(int[] array,int left,int mid,int right){int s1 = left;int s2 = mid + 1;int e1 = mid;int e2 = right;int k = 0;int[] tmpArr = new int[right - left + 1];while(s1 <= e1 && s2 <= e2){if(array[s1] < array[s2]){tmpArr[k++] = array[s1++];}else{tmpArr[k++] = array[s2++];}}while(s1 <= e1){tmpArr[k++] = array[s1++];}while(s2 <= e2){tmpArr[k++] = array[s2++];}for(int i = 0;i < k;i++){array[i + left] = tmpArr[i];//特别注意要加left}}

归并排序总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(N)

4. 稳定性:稳定

总结:

重点掌握:快排,堆排,归并,插入。

计数,基数,桶,这三个排序了解即可(代码不会写都没事不考的)

 

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

 

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

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

相关文章

口腔助手|口腔挂号预约小程序|基于微信小程序的口腔门诊预约系统的设计与实现(源码+数据库+文档)

口腔小程序目录 目录 基于微信小程序的口腔门诊预约系统的设计与实现 一、前言 二、系统功能设计 三、系统实现 1、小程序前台界面实现 2、后台管理员模块实现 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、最新…

ASP.NET Core Web API 流式返回,实现ChatGPT逐字显示

&#x1f3c6;作者&#xff1a;科技、互联网行业优质创作者 &#x1f3c6;专注领域&#xff1a;.Net技术、软件架构、人工智能、数字化转型、DeveloperSharp、微服务、工业互联网、智能制造 &#x1f3c6;欢迎关注我&#xff08;Net数字智慧化基地&#xff09;&#xff0c;里面…

关于创建vue项目报错command failed: npm install --loglevel error

一、首先 在这个目录下有个文件叫.vuerc 二、其次 进去之后把里面的"useTaobaoRegistry": false,修改下&#xff0c;我之前是true&#xff0c;后来改成了false才成功。

Linux下的多用户管理和认证:从入门到精通(附实例)

Linux操作系统以其强大的多用户管理和认证机制而著称。这种机制不仅允许多个用户同时登录并执行各种任务&#xff0c;还能确保每个用户的数据安全和隐私。本文将通过一系列实例&#xff0c;带你逐步掌握Linux下的多用户管理和认证。 一、Linux多用户管理的基础知识 在Linux中&…

EasyCaptcha,开源图形验证码新标杆!

引言&#xff1a; 随着互联网的普及&#xff0c;验证码已成为网站和应用程序中不可或缺的安全组件。它能够有效地防止自动化攻击、垃圾邮件和机器人活动。在众多验证码解决方案中&#xff0c;Easy-captcha以其简单易用和高度可定制的特点受到了开发者的青睐。本文将指导读者如…

推荐系统|召回05_矩阵补充、最近邻查找

文章目录 矩阵补充Matrix Completion模型结构模型训练模型存储 矩阵补充Matrix Completion 模型结构 通过用户ID和物品ID分别找到对应的向量&#xff0c;然后去做内积&#xff0c;内积的数值可以去衡量匹配的程度。 不共享参数的意思是指用户ID和物品ID使用不同的Embedding L…

【计算几何】给定一组点的多边形面积

目录 一、说明二、有序顶点集三、无序顶点集3.1 凸多边形3.2 非凸多边形 四、结论 ​ 一、说明 计算多边形面积的方法有很多种。众所周知的多边形&#xff08;如三角形、矩形、正方形、梯形等&#xff09;的面积可以使用简单的数学公式计算。在这篇文章中&#xff0c;我将讨论…

《UE5_C++多人TPS完整教程》学习笔记2 ——《P3 多人游戏概念(Multiplayer Concept)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P3 多人游戏概念&#xff08;Multiplayer Concept&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff08;也是译…

图灵日记--MapSet字符串常量池反射枚举Lambda表达式泛型

目录 搜索树概念实现性能分析和 java 类集的关系 搜索概念及场景模型 Map的使用Map常用方法 Set的说明常见方法说明 哈希表冲突-避免-负载因子调节冲突-解决-闭散列冲突-解决-开散列/哈希桶冲突严重时的解决办法 实现和 java 类集的关系 字符串常量池String对象创建intern方法 …

SpringCloud-Eureka服务注册中心测试实践

5. Eureka服务注册中心 5.1 什么是Eureka Netflix在涉及Eureka时&#xff0c;遵循的就是API原则.Eureka是Netflix的有个子模块&#xff0c;也是核心模块之一。Eureka是基于REST的服务&#xff0c;用于定位服务&#xff0c;以实现云端中间件层服务发现和故障转移&#xff0c;服…

Junit5基础教程

文章目录 一&#xff0c;导入依赖二&#xff0c;基本功能一、常用断言二、执行顺序和常用注解1、通过BeforeAll类的注解来保证顺序2、通过order注解来保证执行顺序 三、依赖测试四、参数化测试五、测试套件SelectPackages、IncludePackages、SelectClasses、IncludeTags等注解的…

C语言printf函数详解..

1.printf函数解析 前面我们有讲过printf函数的格式为&#xff1a; printf(“占位1 占位2 占位3……”, 替代1, 替代2, 替代3……); 今天我们进一步深入的解析一下这个函数 2.printf函数的特点 1.printf函数是一个变参函数(即参数的数量和类型都不确定) 2.printf函数的第一个…

【MySQL】——数值函数的学习

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-Z1fAnfrxGD7I5gqp {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

bugku 1

Flask_FileUpload 文件上传 先随便传个一句话木马 看看回显 果然不符合规定 而且发现改成图片什么的都不行 查看页面源代码&#xff0c;发现提示 那应该就要用python命令才行 试试ls 类型要改成图片 cat /flag 好像需要密码 bp爆破 根据提示&#xff0c;我们先抓包 爆破 …

ChatGPT高效提问—prompt常见用法(续篇九)

ChatGPT高效提问—prompt常见用法(续篇九) ​ 如何准确地向大型语言模型提出问题,使其更好地理解我们的意图,从而得到期望的答案呢?编写有效的prompt的技巧,精心设计的prompt,获得期望的的答案。 1.1 增加条件 ​ 在各种prompt技巧中,增加条件是最常用的。在prompt中…

MOMENTUM: 1

攻击机 192.168.223.128 目标机 192.168.223.146 主机发现 nmap -sP 192.168.223.0/24 端口扫描 nmap -sV -p- -A 192.168.223.146 开启了22 80端口 看一下web界面 随便打开看看 发现这里有个参数id&#xff0c;sql尝试无果&#xff0c;发现写入什么&#xff0c;网页显示…

【数据结构】11 堆栈(顺序存储和链式存储)

定义 可认为是具有一定约束的线性表&#xff0c;插入和删除操作都在一个称为栈顶的端点位置。也叫后入先出表&#xff08;LIFO&#xff09; 类型名称&#xff1a;堆栈&#xff08;STACK&#xff09; 数据对象集&#xff1a; 一个有0个或者多个元素的有穷线性表。 操作集&#…

Obsidian Publish的开源替代品Perlite

前几天就有网友跟我说&#xff0c;freenom 的免费域名不可用了&#xff0c;10 号的时候老苏进后台看了一下&#xff0c;还有一半的域名显示为 ACTIVE&#xff0c;似乎是以 2024年6月 为限。但到 11 号&#xff0c;老苏发现博客 (https://laosu.cf) 已经访问不了了&#xff0c;这…

【Linux】信号保存与信号捕捉处理

信号保存与信号捕捉 一、信号保存1. 信号的发送2. 理解信号保存&#xff08;1&#xff09;信号保存原因&#xff08;2&#xff09;信号保存概念 3. 信号保存系统接口&#xff08;1&#xff09;sigset_t&#xff08;2&#xff09;sigprocmask()&#xff08;3&#xff09;sigpend…

GraphicsMagick 的 OpenCL 开发记录(三十七)

文章目录 如何写ScaleImage()的硬件加速函数&#xff08;十一&#xff09; <2022-05-06 周五> 如何写ScaleImage()的硬件加速函数&#xff08;十一&#xff09; “如何写ScaleImage()的硬件加速函数&#xff08;十&#xff09;”这里的代码写得比较随意&#xff0c;其中…