十分钟“手撕”七大排序

前言:可以通过目录来找你需要的排序的源代码。先是解释底层原理,后附带代码。 

目录

稳定的概念

一、插入排序

二、希尔排序

三、选择排序

四、堆排序

五、冒泡排序

六、快速排序

七、归并排序

八、排序总结 

额外:计数排序


稳定的概念

稳定性:

相同元素的相对位置排序完没有改变,则为稳定排序。 若排序后相同元素的相对位置发生变化,则为不稳定排序。

一、插入排序

原理如下:

从数据的第2位开始,跟前面的比,找到比该数据小的值插到后面,没有的话插到最前面。

 代码思路:

从第2位开始记为j,让j++把每一位都插到前面去。然后让j的前一位记为i,i--,让j从i开始往前插入。直到全插完。

插入排序特性总结:

    1. 元素集合越接近有序,直接插入排序算法的时间效率越高
    2. 时间复杂度:O(N^2)
    3. 空间复杂度:O(1),它是一种稳定的排序算法
    4. 稳定性:稳定
 

//插入排序/*1. 元素集合越接近有序,直接插入排序算法的时间效率越高2. 时间复杂度:O(N^2)3. 空间复杂度:O(1),它是一种稳定的排序算法4. 稳定性:稳定*/public void insertSort(int[] arr) {for (int i = 1; i < arr.length; i++) {int tmp = arr[i];int j = i - 1;for (; j >= 0; j--) {if (arr[j] > tmp) {arr[j + 1] = arr[j];} else {break;}}//若j<0时,for循环进不来,所以再次放到0下标arr[j + 1] = tmp;}}

二、希尔排序

算法原理:

实际上就是插入排序的优化!插入排序一个一个插太慢了,这时希尔排序就出来了,把数据分多组,让他们先插入排序,然后缩短组数,再排序,循环,直到排完序。

代码思路:

我们先把一组数据总数/2分为gap组,先让gap组排序,然后gap/2,排序 ,循环往复,直到排完。如下图,若gap=2时,我们只需要arr[j]=arr[j+gap]和tmp就能交换插入了,gap=2有2轮,但是剩下一轮就只能再fori一次,若让i++就能交替循环,一次fori就能搞定了。

希尔排序特性:

  时间复杂度:O(N^1.25)到O(1.6 * N^1.25)(业内还没有准确的,只有个大概的)
  稳定性:不稳定

    //希尔排序/*** 因为咋们的gap是按照Knuth提出的方式取值的,而且Knuth进行了大量的试验统计,我们暂时就按照:* O(N^1.25)到O(1.6 * N^1.25)来算。稳定性:不稳定* */public void shellSort(int[] arr) {int gap = arr.length;while (gap > 1) {gap /= 2;shell(gap, arr);}}private void shell(int gap, int[] arr) {for (int i = gap; i < arr.length; i++) {int tmp = arr[i];int j = i - gap;for (; j >= 0; j -= gap) {if (arr[j] > tmp) {arr[j + gap] = arr[j];} else {break;}}arr[j + gap] = tmp;}}

三、选择排序

原理如下:

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

 代码思路:

把第一个值作为tmp,然后i++往后循环,如果有小于tmp的就替换tmp,就找到最小的了,把它放到j(j=i+1)里面。for循环走完arr.length就排完序了。

插入排序特性总结:

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

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

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

4. 稳定性:不稳定

    //选择排序/*1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用2. 时间复杂度:O(N^2)3. 空间复杂度:O(1)4. 稳定性:不稳定*/public void selectSort(int[] arr) {for (int i = 0; i < arr.length; i++) {int minindex = i;for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minindex]) {minindex = j;}}swap(arr, minindex, i);}}private void swap(int[] arr, int minindex, int i) {int tmp = arr[minindex];arr[minindex] = arr[i];arr[i] = tmp;}//选择排序的优化(最大最小一起判断)public void selectSort2(int[] arr) {int left = 0;int right = arr.length - 1;while (left < right) {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);swap(arr, maxindex, right);left++;right--;}}

四、堆排序

原理如下:

因为堆是有顺序的,不是大根堆就是小根堆。就是倒置一下顺序,逆序一下。我们只需要将最后一个排序跟堆首调换一下,然后向下调整,每个都如此,直到排完序。 

 代码思路:

比如大根堆,只需要将end=arr.length-1的元素和堆首元素交换,然后将堆首元素向下调整(找到两个根最大的那一个根,交换),然后end--。

插入排序特性总结:

    1. 堆排序使用堆来选数,效率就高了很多。
    2. 时间复杂度:O(N*logN)
    3. 空间复杂度:O(1)
    4. 稳定性:不稳定

    //堆排序/*1. 堆排序使用堆来选数,效率就高了很多。2. 时间复杂度:O(N*logN)3. 空间复杂度:O(1)4. 稳定性:不稳定*/public void heapSort(int[] arr) {createBigHeap(arr);int end = arr.length - 1;while (end >= 0) {swap(arr, 0, end);siftDown(0, arr, end);end--;}}private void createBigHeap(int[] array) {for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {siftDown(parent, array, array.length);}}private void siftDown(int parent, int[] array, int end) {int child = 2 * parent + 1;while (child < end) {if (child + 1 < end && array[child] < array[child + 1]) {child++;}//child下标 就是左右孩子最大值的下标if (array[child] > array[parent]) {swap(array, child, parent);parent = child;child = 2 * parent + 1;} else {break;}}}private void swap(int[] arr, int minindex, int i) {int tmp = arr[minindex];arr[minindex] = arr[i];arr[i] = tmp;}

五、冒泡排序

原理如下:

一趟一趟来,将小的数和大数交换,每一趟都可以将最大的一个数放到最后。所有趟数走完,将排序完成。

 代码思路:

两层循环,第一层控制趟数为arr.length-1,第二层arr.length-1-i表示一趟中交换的次数。定义一个tmp来交换,如果后面的数比前面的数小,则交换,如果大于,就用大于的这个数和后面交换。

优化:可能循环一次就排完序了,剩下的就浪费时间了。我们可以定义一个flg,如果一趟下来都不需要交换,证明完成了,结束2层循环。

插入排序特性总结:

    1. 冒泡排序是一种非常容易理解的排序
    2. 时间复杂度:O(N^2)
    3. 空间复杂度:O(1)
    4. 稳定性:稳定

    //冒泡排序
/*1. 冒泡排序是一种非常容易理解的排序2. 时间复杂度:O(N^2)3. 空间复杂度:O(1)4. 稳定性:稳定
*/public void buddleSort(int[] arr) {for (int i = 0; i < arr.length - 1; i++) {boolean flg = false;for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {swap(arr, j, j + 1);flg = true;}}if (!flg) {break;}}}

六、快速排序

原理如下:

找基准,通常是第一个元素,然后小于它的放左边,大于他的放右边。然后下一组又是这一组的第一个,直到所有元素都排完序。

 代码思路:

1.递归:每次递归都找第一个为基准,然后<基准,放左边;>基准放右边。直到start<end,就排完序。

2.非递归:

  1. 将待排序的序列的首尾元素的下标入栈。
  2. 当栈不为空时,取出栈顶的下标,将序列的首尾元素作为基准值进行分区操作。
  3. 分区操作的目的是将比基准值小的元素放到基准值的左边,比基准值大的元素放到基准值的右边,并返回基准值的下标。
  4. 将分区操作后的左右子序列的首尾元素的下标入栈,注意先将右子序列的下标入栈,再将左子序列的下标入栈。
  5. 重复步骤2-4,直到栈为空。

优化:

1. 三数取中法选key

2. 递归到小的子区间时,可以考虑使用插入排序

插入排序特性总结:

    1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
    2. 时间复杂度:O(N*logN)
    3. 空间复杂度:O(logN)
    4. 稳定性:不稳定

 

 递归

    //快速排序/*1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序2. 时间复杂度:O(N*logN)3. 空间复杂度:O(logN)4. 稳定性:不稳定
*/public void quickSort(int[] arr) {quick(arr, 0, arr.length - 1);}public void quick(int[] arr, int start, int end) {if (start >= end) {return;}if (end - start + 1 <= 10) {insertSortRange(arr, start, end);return;}//三数取中(优化)int index = midThreeNum(arr, start, end);swap(arr, index, start);int par = partition(arr, start, end);quick(arr, start, par - 1);quick(arr, par + 1, end);}public static void insertSortRange(int[] array, int left, int right) {for (int i = left + 1; 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 int partitionHoard(int[] arr, int left, int right) {int tmp = arr[left];int i = left;while (left < right) {while (left < right && arr[right] >= tmp) {right--;}while (left < right && arr[left] <= tmp) {left++;}swap(arr, left, right);}swap(arr, left, i);return left;}//挖坑法public int partition(int[] arr, int left, int right) {int tmp = arr[left];while (left < right) {while (left < right && arr[right] >= tmp) {right--;}arr[left] = arr[right];while (left < right && arr[left] <= tmp) {left++;}arr[right] = arr[left];}arr[left] = tmp;return left;}//返回值是中位数的下标(优化快排)private static int midThreeNum(int[] array, int left, int right) {int mid = (left + right) / 2;if (array[left] < array[right]) {if (array[mid] < array[left]) {return left;} else if (array[mid] > array[right]) {return right;} else {return mid;}} else {if (array[mid] < array[right]) {return right;} else if (array[mid] > array[left]) {return left;} else {return mid;}}}

非递归 

   //非递归快速排序public void quickSortNor(int[] arr) {Stack<Integer> stack = new Stack<>();int left = 0;int right = arr.length - 1;int par = partition(arr, left, right);if (par > left + 1) {stack.push(left);stack.push(par - 1);}if (par < right - 1) {stack.push(par + 1);stack.push(right);}while (!stack.isEmpty()) {right = stack.pop();left = stack.pop();par = partition(arr, left, right);if (par > left + 1) {stack.push(left);stack.push(par - 1);}if (par < right - 1) {stack.push(par + 1);stack.push(right);}}}

七、归并排序

原理如下:

将一组数据递归分成2对,4对....直到分成单一的元素。最后一对一对排序后回来...4对,2对,1对给它排序,直到排完序。

 代码思路:

递归把数据都分成单一的元素,然后合并,合并的话需要创建一个新的数组tmp,定义s1,e1,s2,e2表示分成2份的数组的头和尾,让s1和s2比较,如果s1比s2小,让s1进入tmp数组,否则反之。若一份数组走完了,另一份数组没有,则让另一份数组while循环进入tmp。

插入排序特性总结:

    1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
    2. 时间复杂度:O(N*logN)
    3. 空间复杂度:O(N)
    4. 稳定性:稳定

    //合并排序
/*1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。2. 时间复杂度:O(N*logN)3. 空间复杂度:O(N)4. 稳定性:稳定*/public void mergeSort(int[] arr) {mergeSortFun(arr, 0, arr.length - 1);}public void mergeSortFun(int[] arr, int left, int right) {if (left >= right) {return;}int mid = (right + left) / 2;mergeSortFun(arr, left, mid);mergeSortFun(arr, mid + 1, right);//合并merge(arr, left, mid, right);}private void merge(int[] arr, int left,int mid, int right) {int k = 0;int[] tmp = new int[right - left + 1];int s1 = left;int e1 = mid;int s2 = mid + 1;int e2 = right;while (s1 <= e1 && s2 <= e2) {if (arr[s1] <= arr[s2]) {tmp[k++] = arr[s1++];} else {tmp[k++] = arr[s2++];}}while (s1 <= e1) {tmp[k++] = arr[s1++];}while (s2 <= e2) {tmp[k++] = arr[s2++];}//拷贝for (int i = 0; i < k; i++) {arr[i + left] = tmp[i];}}//合并排序非递归public void mergeSortNor(int[] array) {int gap = 1;while (gap < array.length) {for (int i = 0; i < array.length; i = i + 2 * gap) {int left = i;int mid = left + gap - 1;if (mid >= array.length) {mid = array.length - 1;}int right = mid + gap;if (right >= array.length) {right = array.length - 1;}merge(array, left, mid, right);}gap *= 2;}}

八、排序总结 

排序方法最好平均最坏时间复杂度空间复杂度
冒泡排序O(N)O(N^2)O(N^2)O(1)稳定
插入排序O(N)O(N^2)O(N^2)O(1)稳定
选择排序O(N^2)O(N^2)O(N^2)O(1)不稳定
希尔排序O(N)O(N^1.3)O(n^2)O(1)不稳定
堆排序O(N * log(N))O(N * log(N))O(N * log(N))O(1)不稳定
快速排序O(N * log(N))O(N * log(N))O(N^2)O(log(N)) ~ O(N)不稳定
归并排序O(N * log(N))O(N * log(N))O(N * log(N))O(N)稳定

额外:计数排序

    //计数排序
/*1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。2. 时间复杂度:O(MAX(N,范围))3. 空间复杂度:O(范围)4. 稳定性:稳定*/public void countSort(int[] array) {//1. 遍历数组 求最大值 和 最小值int maxVal = array[0];int minVal = array[0];for (int i = 0; i < array.length; i++) {if (maxVal < array[i]) {maxVal = array[i];}if (minVal > array[i]) {minVal = array[i];}}//2. 定义count数组int[] count = new int[maxVal - minVal + 1];//3. 遍历array数组 把值 放入 计数数组当中for (int i = 0; i < array.length; i++) {int val = array[i];//98count[val - minVal]++;}//4. 以上3步完成之后,计数数组 已经存好了对应的数据// 接下来 开始遍历 计数数组int index = 0;//array的下标for (int i = 0; i < count.length; i++) {while (count[i] > 0) {array[index] = i + minVal;index++;count[i]--;}}}

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

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

相关文章

Qt MV架构-委托类

一、基本概念 与MVC模式不同&#xff0c;MV视图架构中没有包含一个完全分离的组件来处理与用户的交互。 一般地&#xff0c;视图用来将模型中的数据显示给用户&#xff0c;也用来处理用户的输入。为了获得更高的灵活性&#xff0c;交互可以由委托来执行。 这些组件提供了输入…

gradle学习及问题

一、下载安装 参考&#xff1a;https://blog.csdn.net/chentian114/article/details/123344839 1、下载Gradle并解压 安装包&#xff1a;gradle-6.7-bin.zip 可以在idea的安装目录查看自己适配的版本 路径&#xff1a;D:\IDEA2021.3\plugins\gradle\lib 下载地址&#xff1a…

16_网络IPC2-寻址

进程标识 字节序 采用大小模式对数据进行存放的主要区别在于在存放的字节顺序&#xff0c;大端方式将高位存放在低地址&#xff0c;小端方式将高位存放在高地址。 采用大端方式进行数据存放符合人类的正常思维&#xff0c;而采用小端方式进行数据存放利于计算机处理。到目前…

python用selenium网页模拟时xpath无法定位元素解决方法2

有时我们在使用python selenium xpath时&#xff0c;无法定位元素&#xff0c;红字显示no such element。上一篇文章写了1种情况&#xff0c;是包含iframe的&#xff0c;详见https://blog.csdn.net/Sixth5/article/details/140342929。 本篇写第2种情况&#xff0c;就是xpath定…

Linux 线程初步解析

1.线程概念 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控制序列。在linux中&#xff0c;由于线程和进程都具有id,都需要调度等等相似性&#xff0c;因此都可以用PCB来描述和控制,线程含有PCB&am…

人类或是低等生物?

自工业革命以来&#xff0c;人类对自然资源的消耗日益加剧&#xff0c;引发了对未来可持续性的深刻担忧。然而&#xff0c;一项振奋人心的发现为人类提供了新的希望——一颗名为LHS 1140 b的超级地球&#xff0c;它位于距离地球约48光年的鲸鱼座&#xff0c;由詹姆斯韦布空间望…

uniapp字符串转base64,无需导入依赖(多端支持)

使用示例 import { Base64Encode, Base64Decode } from "@/utils/base64.js" base64.js const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";export const Base64Encode = (text)

Qt Creator的好用的功能

&#xff08;1&#xff09;ctrlf&#xff1a; 在当前文档进行查询操作 &#xff08;2&#xff09;f3: 找到后&#xff0c;按f3&#xff0c;查找下一个 &#xff08;3&#xff09;shiftf3: 查找上一个 右键菜单&#xff1a; (4)f4&#xff1a;在…

使用vcXsrv可视化pcl文件

1、下载vcXsrc程序 2、按下面步骤配置 3、按上面操作后&#xff0c;在运行菜单就能看到它在运行了 4、去wsl中配置&#xff0c;即设置环境变量 vim ~/.bashrc # 设置连接windows的VcXsrv export DISPLAY192.168.1.100:0.0 #&#xff08;192.168.1.100是我windows的ip&#x…

信创学习笔记(四),信创之数据库DB思维导图

创作不易 只因热爱!! 热衷分享&#xff0c;一起成长! “你的鼓励就是我努力付出的动力” 一. 信创学习回顾 1.信创内容 信创内容思维导图 2.信创之CPU芯片架构 信创之CPU芯片架构思维导图 3.信创之操作系统OS 信创之操作系统OS思维导图 二. 信创之国产数据库DB思维导图 …

LAST_INSERT_ID使用方法-(DM8达梦数据库)

LAST_INSERT_ID使用方法 - DM8达梦数据库 1 示例 11.1 创建表1.2 结果集 2 示例 22.1 创建表2.2 结果集 3 达梦数据库学习使用列表 1 示例 1 1.1 创建表 DROP TABLE AT240715; CREATE TABLE "SYSDBA"."AT240715" ( "ID" INT PRIMARY KEY AUTO_…

Autosar Dcm配置-0x28服务ComControl-基于ETAS软件

文章目录 前言DcmDcmDsdDcmDspBswMBswMModeRequestPortBswMModeConditionBswMLogicalExpressionBswMActionBswMActionListBswMRule总结前言 0x28服务主要用来控制非诊断报文的通讯,一般在刷写预编程过程中,用来禁止APP的通信报文,可以减少总线负载率,提高刷写成功率。本文…

数据结构之线性表表示集合详解与示例(C,C#,C++)

文章目录 基本特征线性表的特点&#xff1a;线性表的表示方法&#xff1a;C、C#和C语言如何实现一个线性表表示集合1. C实现2. C#实现3. C实现 总结 线性表是计算机数据结构中的一个基本概念&#xff0c;它是一种最简单的抽象数据类型。在线性表中&#xff0c;数据元素之间的关…

pip install安装第三方库 error: Microsoft Visual C++ 14.0 or greater is required

原因&#xff1a; 在windows出现此情况的原因是pip安装的库其中部分代码不是python而是使用C等代码编写&#xff0c;我们安装这种类型的库时需要进行编译后安装。 安装Microsoft C Build Tools软件&#xff0c;但这种方式对于很多人来说过于笨重。&#xff08;不推荐&#xf…

VUE:跨域配置代理服务器

//在vite.config。js中&#xff0c;同插件配置同级进行配置server:{proxy:{"/myrequest":{//代理域名&#xff0c;可自行修改target:"https://m.wzj.com/",//访问服务器的目标域名changeOrigin:true,//允许跨域configure:(proxy,options) > {proxy.on(&…

【Django+Vue3 线上教育平台项目实战】登录功能模块之短信登录与钉钉三方登录

文章目录 前言一、几个关键概念1.HTTP无状态性2.Session机制3.Token认证4.JWT 二、通过手机号验证码登录1.前端短信登录界面2.发送短信接口与短信登录接口3.Vue 设置interceptors拦截器4. 服务端验证采用自定义中间件方式实现5. 操作流程及效果图如下&#xff1a; 三、通过第三…

Flychat:跨越距离的心灵桥梁

在当今这个数字化时代&#xff0c;人们的沟通方式变得越来越多样化&#xff0c;而移动设备的普及更是极大地推动了这一趋势。我们几乎可以随时随地与他人进行交流&#xff0c;分享生活的点滴。然而&#xff0c;在享受这些便捷的同时&#xff0c;我们也时常会遇到一些小困扰——…

Rust 使用 panic! 还是不用 panic!

使用 panic! 还是不用 panic! 那么&#xff0c;该如何决定何时应该 panic! 以及何时应该返回 Result 呢&#xff1f;如果代码 panic&#xff0c;就没有恢复的可能。你可以选择对任何错误场景都调用 panic!&#xff0c;不管是否有可能恢复&#xff0c;不过这样就是你代替调用者…

年化18.9%的创业板趋势策略,使用模块化策略模板重构(代码+数据)

原创文章第590篇&#xff0c;专注“AI量化投资、世界运行的规律、个人成长与财富自由"。 昨天咱们分享的文章&#xff1a;”以交易为生“&#xff0c;基础设施很重要。 传统backtrader写策略的步骤是如下&#xff1a; 1、定义因子&#xff0c;比如动量roc&#xff1a; …

基于LSTM及其变体的回归预测

1 所用模型 代码中用到了以下模型&#xff1a; 1. LSTM&#xff08;Long Short-Term Memory&#xff09;&#xff1a;长短时记忆网络&#xff0c;是一种特殊的RNN&#xff08;循环神经网络&#xff09;&#xff0c;能够解决传统RNN在处理长序列时出现的梯度消失或爆炸的问题。L…