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

目录

排序的概念:

排序算法的实现:

插入排序:

希尔排序:

选择排序:

堆排序:

冒泡排序:

快速排序:

快速排序的基本框架:

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://www.rhkb.cn/news/255361.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

优质项目追踪平台一览:助力项目管理与监控

项目追踪平台是现代项目管理中不可或缺的工具&#xff0c;它可以帮助团队高效地跟踪和管理项目进度、任务和资源分配。在当今快节奏的商业环境中&#xff0c;有许多热门的项目追踪平台可供选择。 本文总结了当下热门的项目追踪平台&#xff0c;供您参考~ 1、Zoho Projects&…

【Vue】Vue基础入门

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳重求进&#xff0c;晒太阳 Vue概念 是一个用于构建用户界面的渐进式框架优点&#xff1a;大大提高开发效率缺点&#xff1a;需要理解记忆规则 创建Vue实例 步骤&#xff1a; …

SpringBoot-基础篇03

之前搭建了整个开发环境实现了登录注册&#xff0c;springBoot整合mybatis完成增删改查&#xff0c;今天完成分页查询&#xff0c;使用阿里云oss存储照片等资源&#xff0c;后期会尝试自己搭建分布式文件系统来实现。 一&#xff0c;SpringBootMybatis完成分页查询 1&#xff…

npm 上传一个自己的应用(3) 在项目中导入及使用自己上传到NPM的工具

上文 npm 上传一个自己的应用(2) 创建一个JavaScript函数 并发布到NPM 我们创建了一个函数 并发上了npm 最后 我们这里 我们可以看到它的安装指令 这里 我们可以打开一个vue项目 终端输入 我们的安装指令 npm i 自己的包 如下代码 npm i grtest我们在 node_modules目录 下…

【Linux】构建模块

&#x1f525;博客主页&#xff1a;PannLZ &#x1f38b;系列专栏&#xff1a;《Linux系统之路》 &#x1f94a;不要让自己再留有遗憾&#xff0c;加油吧&#xff01; 文章目录 构建第一个模块1模块的makefile2内核树内构建3内核树外构建 构建第一个模块 可以在两个地方构建模…

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转 控制反转是一种思想 控制反转是为了降低程序耦合度&#xff0c;提高程序扩展力&#xff0c;达到 OCP 原则&#xff0c;达到 DIP 原则 控制反转&#xff0c;反转的是什么&#xff1f; 将对象的创建权利交出去&#xff0c;交给第三方容器负责 将对象和对象之…

Windows下搭建Redis Sentinel

下载安装程序 下载Redis关于Windows安装程序&#xff0c;下载地址 下载成功后进行解压&#xff0c;解压如下&#xff1a; 配置redis和sentinel 首先复制三份redis.windows.conf&#xff0c;分别命名为&#xff1a;redis.6379.conf、redis.6380.conf、redis.6381.conf&…

C++笔记之regex(正则表达式)

C++笔记之regex(正则表达式) ——2024-02-10 ——《C++标准库》(第2版,侯捷译) Page 717 code review! 文章目录 C++笔记之regex(正则表达式)例1:使用正则表达式进行搜索(`std::regex_search`)例2:使用正则表达式进行全文匹配(`std::regex_match`)例3:使用正则表达式…

01-Spring实现重试和降级机制

主要用于在模块调用中&#xff0c;出现失败、异常情况下&#xff0c;仍需要进行重复调用。并且在最终调用失败时&#xff0c;可以采用降级措施&#xff0c;返回一般结果。 1、重试机制 我们采用spring 提供的retry 插件&#xff0c;其原理采用aop机制&#xff0c;所以需要额外…

分享3款开源免费好用的Docker可视化管理工具安装部署教程

文章目录 1.前言2.Docker Desktop3.Portainer3.1 Portainer默认英文版本安装3.2 Portainer汉化版本安装3.3官方镜像说明3.3.1ssl访问3.3.2Nginx反代3.3.3Nginx反代设置子目录3.3.4docker-compose部署 3.4登录 4.DockerUI4.1简介4.2项目地址4.3部署启动命令4.4登录4.5首页 5.总结…

vue day06

1、路由模块封装 2、声明式导航 实现导航高亮效果 直接通过这两个类名对相应标签设置样式 点击a链接进入my页面时&#xff0c;a链接 我的音乐高亮&#xff0c;同时my下的a、b页面中的 我的音乐也有router-link-active类&#xff0c;但没有精确匹配的类&#xff08;只有my页…

Android 粒子喷泉动效

一、前言&#xff1a; 在学习open gl es实现动效的时候&#xff0c;打算回顾了一下用普通的2D坐标系实现粒子效果和 open gl 3d 坐标系的区别&#xff0c;以及难易程度&#xff0c;因此本篇以Canvas 2D坐标系实现了一个简单的demo。 粒子动效原理&#xff1a; 粒子动效本质上…

读完《王志纲谈生涯规划》后感

(点击即可收听) 经常在短视频刷到,这位王志钢老师,在微信读书里面也看到过,于是拜读了一下,这是一本生涯规划书,但更多的是他个人经历的一个描述 有大道理&#xff0c;有些话还是值得认可的 比如&#xff1a;他谈到,想要减少个人乃至社会的悲剧&#xff0c;最好的办法就是尽自己…

svg基础(八)滤镜-feTurbulence(湍流)

feTurbulence&#xff1a;湍流滤镜 湍流滤镜&#xff0c;不稳定气流&#xff0c;能够实现半透明的烟熏或波状图像。 通常用于实现一些特殊的纹理。滤镜利用 Perlin 噪声函数创建了一个图像。噪声在模拟云雾效果时非常有用&#xff0c;能产生非常复杂的质感&#xff0c;利用它可…

【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…

[C++] 如何使用Visual Studio 2022 + QT6创建桌面应用

安装Visual Studio 2022和C环境 [Visual Studio] 基础教程 - Window10下如何安装VS 2022社区版_visual studio 2022 社区版-CSDN博客 安装QT6开源版 下载开源版本QT Try Qt | 开发应用程序和嵌入式系统 | Qt Open Source Development | Open Source License | Qt 下载完成&…

springboot原理

springboot是基于Spring Framework升级的框架从而更加高效的开发主要体现在依赖配置的简化 springboot的起步依赖通过maven依赖传递包含了开发需要的依赖

【Linux】学习-基础IO—下

Linux基础IO—上 重定向 通过上篇的学习&#xff0c;我们了解了文件描述符的分配规则是遍历指针数组&#xff0c;用没有被使用的最小下标作为新的文件描述符&#xff0c;也就是我们可以通过关闭三个标准流文件并使用他们原先所占用的0&#xff0c;1&#xff0c;2描述符。 那…

探索设计模式的魅力:代理模式揭秘-软件世界的“幕后黑手”

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 引言 一、魔法世界 1.1 定义与核心思想 1.2 静态代理 1.3 动态代理 1.4 虚拟代理 1.5 代理模式结构图 1.6 实例展示如何工作&#xff08;场景案例&#xff09; 不使用模式实现 有何问题 使用模式重构示例 二、…

车载电子电器架构 —— 电子电气系统车载功能子系统

车载电子电器架构 —— 电子电气系统车载功能子系统 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 本就是小人物&#xff0c;输了就是输了&#xff0c…