最详细的排序解析,理解七大排序

最详细的排序解析,理解七大排序

mp.weixin.qq.com

点击上方“方志朋”,选择“置顶或者星标”

你的关注意义重大!

注:

  1. lgN在这里为1og2N简写

  2. 为了方便描述,本文默认用int类型比较,从小到大排序

  3. 本文排序算法以java语言实现

  4. 本文的排序都是比较排序

  5. 比较次数和赋值和交换次数有的排序不好分析,可能不准确

一.插入排序

对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

  1. 从第一个元素开始,该元素认为已经被排序;

  2. 取出下一个元素,在已排序的元素序列中从后向前扫描;

  3. 如果已排序元素大于新元素,新元素继续比较前一个元素,直到找到已排序的元素小于或者等于新元素的位置;

  4. 将新元素插入到该位置后;

  5. 重复步骤2~4。

java实现

public static void insertionSort(int[] a){int insertIndex,insertElement;for(int i = 1; i < a.length; i++){ //外层循环,默认第一个元素有序,从第二个元素开始,时间复杂度NinsertIndex = i - 1; //插入的位置,默认有序数列的最后一个元素的位置insertElement = a[i]; //新插入的元素,默认外层循环的元素while(insertIndex >= 0 && a[insertIndex] > insertElement){ //内层循环,只要新元素比待插入位置的元素小就继续,时间复杂度Na[insertIndex + 1] = a[insertIndex]; //比待插入元素大的元素后移一位insertIndex--; //插入位置前移一位}a[insertIndex + 1] = insertElement; //内层循环结束,把新元素放到插入位置后面}
}

插入排序为两层循环嵌套,时间复杂度O(N2),插入排序的while循环是先比较,移动待插入的位置,循环结束才真正交换数据位置。这里需要注意,常用的for循环嵌套进行插入排序会导致每次插入一直和前面元素交换直到插入到待插入位置,上面的内循环用while寻找待插入位置,把其他元素后移的算法更合理,每次插入只一次进行一次交换。因插入排序每次只比较一位,对于逆序较多的数组效率较低,衍生算法希尔排序,会大幅加快逆序交换,后面详细介绍。

二.选择排序

在未排序序列中找到最小元素,放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾,循环直到所有元素均排序完毕。

  1. 初始状态:无序区为R[1..n],有序区为空;

  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R[i..n]。该趟排序从当前无序区中选出最小的记录 R[k],将它与无序区的第1个记录R[i]交换,使R[1..i]和R[i+1..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;

  3. 循环n-1次,排序完成。

java实现

public static void selectionSort(int[] a){int minIndex,temp;for(int i = 0; i < a.length - 1; i++){ //外层循环,从无序区第一个元素开始到数组倒数第二个元素,时间复杂度NminIndex = i; //每次外层循环假设无序区第一个元素是最小元素for(int j = i + 1; j < a.length; j++){	//内层循环,从假设的最小元素的后一个位置开始,到数组最后一个元素,时间复杂度Nif(a[j] < minIndex){ //判断内层循环的元素是否小于假设的最小元素	minIndex = j; //如果比最小元素小,标记该元素的位置为新的最小元素的位置,内层循环完毕,会找出无序区的最小值	}} temp = a[i]; 		a[i] = a[minIndex];a[minIndex] = temp;	//无序区真正最小值和第一个元素交换位置,下一次循环无序区从下一个值开始}
}

选择排序为两层for循环嵌套,内层循环始终去找最小值,放到最前面。交换次数比冒泡排序少很多,所以实际执行效率比冒泡排序快。 衍生算法,双向选择排序(每次循环,同时选出最大值放在末尾,最小值放在前方),可以提高选择效率。

三.冒泡排序

重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换

  1. 初始状态:无序区为R[1..n],有序区为空;

  2. 第i趟排序(i=0,1,2…n-1)开始时,当前无序区和有序区分别为R[0..n-i]和R[n-i+1..n]。对每一对相邻元素进行比较,从开始第一对到结尾的最后一对,如果第一个比第二个大,就交换它们两个,这样在最后的元素应该会是最大的数,使R[1..n-i-1]和R[n-i..n]分别变为记录个数减少1个的新无序区和记录个数增加1个的新有序区;

  3. 循环n-1次,直到排序完成。

java实现

public static void bubbleSort(int[] a){int temp;for(int i = 0; i < a.length - 1; i++){ //外层循环,从数组第一个元素到倒数第二个元素,时间复杂度为Nfor(int j = 0; j < a.length - 1 -i; j++){ //内层循环,从数组第一个元素到剩余的元素(减去有序区的元素)if(a[j] > a[j+1]){ temp = a[j+1];a[j+1] = a[j];a[j] = temp; //相邻元素只要前面的比后面的大,就交换位置}}}
}

冒泡排序在代码实现上是最简单的,不需要什么思考,两层for循环嵌套,比大小交换。因为冒泡通常的例子都是让大的往后移,对于刚接触排序的人来说看来上面可能认为冒泡排序与选择排序是反向操作,其实冒泡排序也可以把小数向前移,这样能明显的看出冒泡排序和选择的排序的不同,针对无序区的元素,冒泡排序总是不断地交换,而选择排序是先找出最小的元素再做一次交换。 衍生算法,鸡尾酒排序,该排序从左往右找出最大值后,再从右往左,找出最小值,类似鸡尾酒搅拌左右循环。在某些情况下,优于冒泡排序,以序列(2,3,4,5,1)为例,鸡尾酒排序只需要访问两次(升序降序各一次 )次序列就可以完成排序,但如果使用冒泡排序则需要四次。

四.希尔排序

插入排序的改进版,优先比较距离远的元素,减少交换次数

1.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1; 2.按增量序列个数k,对序列进行k 趟排序; 3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

java实现

public static void shellSort(int[] a){int h = 1; //希尔排序是使间隔为h的元素有序int temp;while(h < a.length/3) { //while循环,扩大hh = 3*h + 1; //这里用3倍作为希尔排序的间隔,是常用的值,加1是为了防止排序的都是3的倍数}while(h >= 1){ //while循环让h从大到小插入排序for(int i = h; i < a.length; i++){  //从h位置开始,对整个数组遍历,i为插入元素的位置temp = a[i]; 把i当前值保存到temp中for(int j = i - h; j > 0 && a[j] > temp ;j -= h){ //遍历把i每隔h位进行比较,确定插入的位置a[j + h] = a[j];  //如果前面的元素大于temp,把前面元素的值放在后面}a[j + h] = temp; //把原来i的值赋值给前面元素,此时发生了j -=h,实际上j + h等于循环里的j}h = h/3; //更大间隔的插入完成,缩小插入间隔}
}

上面是常用的写法,每次比较,如果前面的更大都会交换,可以优化一下,直接把上面插入算法嵌入内循环,比较的间隔由1改为h,比较的时候只移动插入位置,比较完只需交换一次 java实现

public static void shellSort(int[] a){int h = 1; //希尔排序是使间隔为h的元素有序int insertIndex,insertElement;while(h < a.length/3) { //while循环,扩大hh = 3*h + 1; //这里用3倍作为希尔排序的间隔,是常用的值,加1是为了防止排序的都是3的倍数}while(h >= 1){ //while循环让h从大到小插入排序for(int i = h; i < a.length; i++){  //从h位置开始,对整个数组遍历,i为插入元素的位置insertIndex = i - h; //插入的位置,默认前面间隔h的位置insertElement = a[i]; //新插入的元素,默认外层循环的最后一个元素while(insertIndex >= 0 && a[insertIndex] > insertElement){ //内层循环,只要新元素比待插入位置的元素小就继续a[insertIndex + h] = a[insertIndex]; //比待插入元素大的元素后移h位insertIndex -= h; //插入位置前移h位}a[insertIndex + h] = insertElement; //内层循环结束,把新元素放到插入位置后面}h = h/3; //更大间隔的插入完成,缩小插入间隔}
}

希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N1.5),希尔排序时间复杂度的下界是Nlg2N。希尔排序没有快速排序算法快 O(N(lgN)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O(N²)复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。

五.堆排序

堆就是完全二叉树,分为最大堆和最小堆,最大堆要求节点的元素都要不小于其孩子(最小堆要求节点元素都不大于其左右孩子),对左右孩子的大小关系不做要求,所以处于最大堆的根节点的元素一定是这个堆中的最大值。堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。

1.将初始待排序关键字序列(R1,R2….Rn)构造成最大堆,此堆为初始的无序区; 2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n]; 3.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

java实现

public static void heapSort(int[] a){int N = a.length;for(int k = N/2; k >= 1; k--){ //for循环用来构造堆,最终生成最大堆sink(a,k,N);}while(N > 1){ //循环排序无序区exch(a,1,N--); //堆顶a[1]与堆的最后一个元素a[N]交换位置,并且去掉最后一个元素到有序区,减小新堆sink(a,1,N); //重新生成为最大堆}
}/*** 从上至下堆有序化*/
private static void sink(int[] a,int k,int N){ while(2*k <= N) {int j = 2*k;if(j < N && a[j] < a[j+1]){ //j<n保证j+1不越界,a[j]和a[j+1]是a[k]的左右子节点,这里是为了选取两个子节点较大的一个,a[j]大取a[j],a[j]小取a[j++]>= a[j]) { //如果父节点大于等于值大的子节点,堆有序,终止循环break;	}if(a[k] >= a[j]) { //如果父节点大于等于值大的子节点,堆有序,终止循环break;	}exch(a,k,j); //交换值大的子节点和父节点的值,达到堆有序k = j; //子节点,作为下一个循环的父节点,继续下沉}
}/*** 交换两个元素*/
private static void exch(int[] a,int i,int j){int temp = a[i];a[i] = a[j];a[j] = temp;
}}</n保证j+1不越界,a[j]和a[j+1]是a[k]的左右子节点,这里是为了选取两个子节点较大的一个,a[j]大取a[j],a[j]小取a[j++]>

因为堆的父节点k的子节点为2k和2k+1,下标为0会导致父节点和左子节点都是0,所以上述排序的数组从下标从1开始比较方便。堆排序只需要NlgN的时间,而且算法稳定,只需要少量辅助空间,是最优的利用时间和空间的方法,但因为它的缓存命中率低,应用很少用它,多用于嵌入式。

六.归并排序

递归的把已有序列均分为两个子序列,使子序列有序,合并子序列 1.把长度为n的输入序列分成两个长度为n/2的子序列; 2.对这两个子序列分别采用归并排序; 3.将两个排序好的子序列合并成一个最终的排序序列。

java实现

private static int[] aux; //归并所需的辅助数组
public static void mergeSort(int[] a){aux = new int[a.length];sort(a,0,a.length-1); //开始递归排序
}/*** 递归的排序归并*/	
private static void sort(int[] a,int left,int right){if(right <= left){ //排序从左到右,确保右边比左边大return;}int mid = (left + right)/2; //找出中间位置sort(a,left,mid); //将左半边排序sort(a,mid+1,right); //将右半边排序merge(a,left,mid,right); //归并结果
}/*** 原地归并方法*/
private static void merge(int[] a,int left,int mid,int right){ //将a[left..mid]和a[mid+1..right]归并int i = left,j = mid + 1;  //左右半边起始位置for(int k = left; k <= right; k++){ //将a[left..right]复制到aux[left..right]aux(k) = a(k);}for(int k = left; k <= right; k++){ //归并回到a[left..right]if(i > mid){  //i比mid大代表左半边用完,只有右半边有元素a[k] = aux[j++]; //右边元素给a[k]}else if(j > right){ //j比right大代表右半边用完,只有左半边有元素a[k] = aux[i++]; //左边元素给a[k]}else if(aux[j] < aux[i]){ //如果右边元素大于左边a[k] = aux[j++]; //右边元素给a[k]	}else{	//否则左边大于等于右边a[k] = aux[i++]; //左边元素给a[k]}}
}

归并排序是分治法的典型应用,高效稳定,但是归并排序需要一个数组长度的辅助空间,在空间成本高的时候不适合使用

七.快速排序

通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

1.从数列中挑出一个元素,称为 “基准”(pivot); 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

java实现

public static void quickSort(int[] a){sort(a,0,a.length-1);
}/*** 递归进行快速排序*/
private static void sort(int[] a,int left,int right){if(right <= left){ //排序从左到右,确保右边比左边大return;}int j = partition(a,left,right); //切分sort(a,left,j-1); //将左半边排序sort(a,j+1,right); //将右半边排序
}/*** 快速排序切分*/
private static int partition(int[] a,int left,int right){ int i = left,j = right + 1; //左右扫描指针int v = a[left]; //选取切分元素,这里选第一个,实际数据可以自行挑选while(true){while(a[++i] < v){ //a[i]<v时增大i,只要比v小继续往右扫描 i="=" v="">< a[--j]){ //a[j]>v时减小j,只要比v大继续往左扫描if(j == left){ //扫描到左边则终止break;}}while(v < a[--j]){ //a[j]>v时减小j,只要比v大继续往左扫描if(j == left){ //扫描到左边则终止break;}}if(i >= j){ //如果左右指针交叉,终止循环break; }exch(a,i,j); //不满足上述条件(左边比v大,右边比v小,指针未交叉),左右元素交换位置}exch(a,left,j); //将切分元素v放入正确的位置return j; //a[left..j-1]<=a[j]<=a[j+1..right],并返回j
}/*** 交换两个元素*/
private static void exch(int[] a,int i,int j){int temp = a[i];a[i] = a[j];a[j] = temp;
}</v时增大i,只要比v小继续往右扫描>

快速排序是通常情况下的最优选择,高效,只需要lgN级别的辅助空间,但是快速排序不稳定,受切分点的影响很大

七种排序总结

上面详细介绍了七种排序的实现细节和特点,下面的表格总结了七种排序的各种特征。

 

 

其中插入排序,选择排序,冒泡排序都是简单排序,时间复杂度是O(N2),其中插入排序和冒泡排序适合原始序列有序的数组,选择排序的交换和赋值次数会比较少,可以根据不同环境和数据的实际情况和长度选择具体的排序。整体插入排序优于选择排序优于冒泡排序。希尔排序是插入排序的优化,突破了前三个排序O(N2)的时间复杂度,但是本质还是插入排序,突破比较相邻元素的惯性思维,直接比较一定间隔的元素,大幅度减少了逆序调整的比较次数和交换次数,从而达到比较理想的算法复杂度,适合对中等规模数组进行排序。堆排序是利用了最大堆的特点,始终把堆顶元素(最大元素)和最后一个元素替换,再重新构造最大堆,重复执行达到排序的效果。堆结构的特性让算法的复杂度降低到NlgN级别,但是有不方便索引元素的确定,缓存命中率较低。而归并排序则是充分运用了分治原理,把大问题不断的拆分成小问题,进行排序,再合并小数组达到整体排序的目标,归并排序即高效又可靠,唯一的缺点是需要数组长度的辅助空间,在空间成本低的时候适合使用。快速排序则解决了归并排序占用空间的问题,在数组内部用很小的辅助栈,即可完成对元素的分离,再去解决分离后的更小的数组,正常情况下拥有和归并相同级别的时间复杂度,但是得注意选取好切分元素。 实际上一个复杂的序列可能用不止一种排序,例如分治和快速排序在分割到很小的序列时再进行分割反而效率不如插入排序等简单排序,可以设置一定的阈值,先用分治或者快速排序的方式分割数组,再转换成插入等简单排序完成最终的排序。

希望本文大家加深对排序的理解有所帮助。我的微信公众号:程序之路,会持续发布技术成长文章,欢迎长按下图关注

 

转载于:https://www.cnblogs.com/bigben0123/p/10414202.html

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

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

相关文章

xp删除管理员账户_在Windows XP中从登录屏幕删除用户帐户

xp删除管理员账户So you login to your computer every single day, but there’s more than one account to choose from… either because you got the computer from somebody else, or some software package added a user account that you really don’t want to see. So…

Pycharm下将py文件打包成exe文件

1. 在PyCharm下安装PyInstaller 1. 首先&#xff0c;打开自己要发布的工程 2. 点击底部的【Terminal】打开终端&#xff0c;中输入命令pip install pyinstaller后回车&#xff0c;如图所示进行安装 3. 输入命令 pyinstaller&#xff0c;回车显示安装成功 4. 输入命令 pyinstall…

什么是自然语言处理,它如何工作?

NicoElNino/Shutterstock.comNicoElNino / Shutterstock.comNatural language processing enables computers to process what we’re saying into commands that it can execute. Find out how the basics of how it works, and how it’s being used to improve our lives. 自…

GIT速查手册

为什么80%的码农都做不了架构师&#xff1f;>>> 一、GIT 1.1 简单配置 git是版本控制系统&#xff0c;与svn不同的是git是分布式&#xff0c;svn是集中式 配置文件位置 # 配置文件 .git/config 当前仓库的配置文件 ~/.gitconfig 全局配置文件# 查看所有配置项 git …

4-3逻辑非运算符及案例 4-4

创建类 LoginDemo3 这里取反 !(n%30) package com.imooc.operator; import java.util.Scanner;public class LoginDemo3 {public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println("请输入一个整数");Scanner scnew Scanner(…

assistant字体_如何使用Google Assistant设置和致电家庭联系人

assistant字体Google谷歌Google Home and Nest smart speakers and displays allow you to make calls without using your phone. By setting up “Household Contacts,” anyone in your home can easily call friends and family members with Google Assistant-enabled dev…

Accoridion折叠面板

详细操作见代码&#xff1a; <!doctype html> <html><head><meta charset"UTF-8"><title></title><meta name"viewport" content"widthdevice-width,initial-scale1,minimum-scale1,maximum-scale1,user-scal…

skype快捷键_每个Skype键盘快捷键及其用法

skype快捷键Roberto Ricca/Shutterstock罗伯托里卡/ ShutterstockGet familiar with Skype’s unique keyboard shortcuts that will allow you to quickly change your settings, alter your interface, and control your communications. Use these hotkeys and become a Sky…

YouTube键盘快捷键:速查表

Google’s video website wouldn’t be complete without all sorts of useful buttons and hidden commands that aren’t immediately obvious. Use this hotkey cheat sheet to quickly navigate YouTube and gain better control over your video browsing experience. 如果…

MySQL服务读取参数文件my.cnf的规律研究探索

在MySQL中&#xff0c;它是按什么顺序或规律去读取my.cnf配置文件的呢&#xff1f;其实只要你花一点功夫&#xff0c;实验测试一下就能弄清楚&#xff0c;下面的实验环境为5.7.21 MySQL Community Server。其它版本如有不同&#xff0c;请以实际情况为准。 其实&#xff0c;MyS…

将组策略编辑器添加到控制面板

If you find yourself using the Group Policy Editor all the time, you might have wondered why it doesn’t show up in the Control Panel along with all the other tools. After many hours of registry hacking, I’ve come up with a registry tweak to let you do ju…

Exchange Server 2016管理系列课件50.DAG管理之激活数据库副本

激活邮箱数据库副本是将特定被动副本指定为邮箱数据库的新主动副本的过程。我们将此过程称为数据库切换。数据库切换过程是指卸除当前的活动数据库&#xff0c;然后在指定的服务器上将相应的数据库副本作为新的活动邮箱数据库副本进行装载。成为活动邮箱数据库的数据库副本必须…

常见设计模式 (python代码实现)

1.创建型模式 单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种常用的软件设计模式&#xff0c;该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中&#xff0c;某个类只能出现一个实例时&#xff0c;单例对象就能派上用场。 比如&#…

记录一次解决httpcline请求https报handshake_failure错误

概述 当使用httpclinet发起https请求时报如下错误&#xff1a; javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failureat com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)at com.sun.net.ssl.internal.ssl.Alerts.getSSLExcep…

桌面程序explorer_备份Internet Explorer 7搜索提供程序列表

桌面程序explorerIf you are both an IE user and a fan of using custom search providers in your search box, you might be interested to know how you can back up that list and/or restore it on another computer. Yes, this article is boring, but we’re trying to…

GreenPlum数据库故障恢复测试

本文介绍gpdb的master故障及恢复测试以及segment故障恢复测试。 环境介绍&#xff1a;Gpdb版本&#xff1a;5.5.0 二进制版本操作系统版本&#xff1a; centos linux 7.0Master segment: 192.168.1.225/24 hostname: mfsmasterStadnby segemnt: 192.168.1.227/24 hostname: ser…

书评:Just the Computer Essentials(Vista)

Normally we try and focus on articles about how to customize your computer, but today we’ll take a break from that and do a book review. This is something I’ve not done before, so any suggestions or questions will be welcomed in the comments. 通常&#x…

python学习

为了学会能学&#xff0c;不负时间&#xff0c;为了那简练的美&#xff01; 为了一片新天地。 /t 对齐 python : # 99乘法表i 0while i < 9 : i 1 j 0 while j < i : j 1 print(j ,* , i,"" , i*j , end\t) #空格不能对齐 制表符…

hey 安装_如何在助理扬声器上调整“ Hey Google”的灵敏度

hey 安装Vantage_DS/ShutterstockVantage_DS / ShutterstockThe Google Assistant is a useful tool that allows you to control your smart home, check the weather, and more. Unfortunately, the Assistant might not hear you in a noisy environment or it might activa…

EXCEL如何进行多条件的数据查找返回

在使用EXCEL时经常会碰到一个表里的同一款产品每天的销量都不一样&#xff0c;然后我们需要查导出每一款产品每天的销量&#xff0c;即一对多条件查找。这个教复杂&#xff0c;我们要用到好几个函数的综合&#xff0c;下面小编来教你吧。 工具/原料 EXCEL软件&#xff08;本文使…