排序算法总结

常见的十大排序算法
八大排序算法分类

一、直接插入排序Insertion Sort

插入排序的设计初衷是往有序的数组中快速插入一个新的元素。它的算法思想是:把要排序的数组分为了两个部分, 一部分是数组的全部元素(除去待插入的元素), 另一部分是待插入的元素; 先将第一部分排序完成, 然后再插入这个元素. 其中第一部分的排序也是通过再次拆分为两部分来进行的.

插入排序由于操作不尽相同, 可分为 直接插入排序 , 折半插入排序(又称二分插入排序), 链表插入排序 , 希尔排序

基本思想
将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
①. 从第一个元素开始,该元素可以认为已经被排序
②. 取出下一个元素,在已经排序的元素序列中从后向前扫描
③. 如果该元素(已排序)大于新元素,将该元素移到下一位置
④. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
⑤. 将新元素插入到该位置后
⑥. 重复步骤②~⑤

如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。可以认为是插入排序的一个变种,称为二分查找插入排序。

public static void insertionSort(int[] arr){for( int i = 1; i < arr.length; i++ ) {int temp = arr[i];    // 取出下一个元素,在已经排序的元素序列中从后向前扫描for( int j = i; j >= 0; j-- ) {if( j > 0 && arr[j-1] > temp ) {arr[j] = arr[j-1];    // 如果该元素(已排序)大于取出的元素temp,将该元素移到下一位置System.out.println("Temping:  " + Arrays.toString(arr));} else {// 将新元素插入到该位置后arr[j] = temp;System.out.println("Sorting:  " + Arrays.toString(arr));break;}}}
}// 交换次数较多的实现
public static void insertionSort(int[] arr){for( int i=0; i<arr.length-1; i++ ) {for( int j=i+1; j>0; j-- ) {if( arr[j-1] <= arr[j] )break;int temp = arr[j];      //交换操作arr[j] = arr[j-1];arr[j-1] = temp;System.out.println("Sorting:  " + Arrays.toString(arr));}}
}

复杂度分析:

最好情况下,排序前对象已经按照要求的有序。比较次数(KCN):n−1n−1;移动次数(RMN)为00。则对应的时间复杂度为O(n)。
最坏情况下,排序前对象为要求的顺序的反序。第i趟时第i个对象必须与前面i个对象都做排序码比较,并且每做1次比较就要做1次数据移动(从上面给出的代码中看出)。对应的时间复杂度为O(n2)。
如果排序记录是随机的,那么根据概率相同的原则,平均时间复杂度为O(n2)。

二、希尔排序Shell Sort

希尔排序,也称递减增量排序算法

基本思想

将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次再将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
即先以大步长依次对两个元素排序,再缩小步长进行排序,直至步长为1。步长依次减半。
一般来说最简单的步长取值是初次取数组长度的一半为增量,之后每次再减半,直到增量为1。

算法描述:
1、选择一个增量序列t1,t2,…tk,(一般初次取数组半长,之后减半,直到增量为1)
2、按增量序列个数k,对序列进行k次排序
3、每趟排序,根据增量ti,将待排序序列分割为若干个长度为m的子序列,分别进行排序。

简单实现

public static void shellSort(int[] arr){int gap = arr.length / 2;for (; gap > 0; gap /= 2) {      //不断缩小gap直到为1for (int j = 0; (j+gap) < arr.length; j++){     //根据当前gap进行组内插入排序for(int k = 0; (k+gap)< arr.length; k += gap){//依次进行k和k+map俩个元素之间的比较if(arr[k] > arr[k+gap]) {int temp = arr[k+gap];      //交换arr[k+gap] = arr[k];arr[k] = temp;System.out.println("    Sorting:  " + Arrays.toString(arr));}}}}
}

官方实现

public static void shell_sort(int[] arr) {int gap = 1, i, j, len = arr.length;int temp;while (gap < len / 3)gap = gap * 3 + 1;     //gap选取for (; gap > 0; gap /= 3) {for (i = gap; i < len; i++) {temp = arr[i];for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)arr[j + gap] = arr[j];arr[j + gap] = temp;}}
}

时间复杂度

三、选择排序

基本思想:比较+交换
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。

算法描述

1、在未排序序列中,找到最小的元素;
2、若最小元素不是未排序序列的第一个元素,则将其和第一个元素互换;
3、从余下的N-1个元素中,找出最小的元素,重复1、2步,直到排序结束。

代码实现

public static void selectionSort(int[] arr){for(int i = 0; i < arr.length-1; i++){int min = i;  //初始化最小元素为当前ifor(int j = i+1; j < arr.length; j++){    //选出之后待排序中的最小值的位置if(arr[j] < arr[min]){min = j;}}if(min != i){int temp = arr[min];      //若最小值不是当前i,交换arr[min] = arr[i];arr[i] = temp;System.out.println("Sorting:  " + Arrays.toString(arr));}}
}

选择排序需要遍历很多次,所以时间复杂度很高。

复杂度分析

四、堆排序

最小堆/最大堆

将序列对应的二维数组看成是一个完全二叉树,堆的含义:完全二叉树中任何一个**父节点的值均不大于(或不小于)其左、右子节点的值****。

基本思想
以大顶堆为例,堆排序即将待排序的序列构造成一个堆,选出堆中最大的移走,将剩余元素构造成堆,找出最大的移走,重复直至有序。

算法描述
代码实现
堆排序需要两个过程:一是建立堆,二是将堆顶元素与堆的最后一个元素互换,所以包含两个元素,一是建堆函数,二是反复调用建堆函数对未排序序列建立堆以选出最大值。

总结:
最大堆调整:将堆的末端子节点作调整,使得子节点始终小于父节点;因为将堆顶与末端子节点互换之后,堆顶可能会违反堆特性,所以需要进行调整。
创建最大堆:将堆所有数据重新排序;
堆排序:移除根节点即最大值,并做最大堆调整的递归运算。

对于堆节点的访问:
1、父节点i的左子节点:(2i+1)
2、父节点i的右子节点:(2
i+2)
3、子i节点i的父节点:floor((i-1)/2)

public static void heapSort(int[] arr){for(int i = arr.length; i > 0; i--){//堆i建立是通过n次调用最大堆调整函数实现的,O(n)max_heapify(arr, i);int temp = arr[0];      //堆顶元素与末元素互换arr[0] = arr[i-1];arr[i-1] = temp;}
}private static void max_heapify(int[] arr, int limit){if(arr.length <= 0 || arr.length < limit) return;//最大堆调整,沿着堆的父子节点进行调整,执行次数为深度,时间复杂度为O(nlgn)int parentIdx = limit / 2;//父节点for(; parentIdx >= 0; parentIdx--){if(parentIdx * 2 >= limit){continue;}int left = parentIdx * 2;       //左子节点位置int right = (left + 1) >= limit ? left : (left + 1);    //右子节点位置int maxChildId = arr[left] >= arr[right] ? left : right;   //子节点最大值if(arr[maxChildId] > arr[parentIdx]){   //若子节点大于父节点,则交换父节点与左右子节点中的最大值int temp = arr[parentIdx];arr[parentIdx] = arr[maxChildId];arr[maxChildId] = temp;}}System.out.println("Max_Heapify: " + Arrays.toString(arr));
}

五、冒泡排序

重复的走访要排序的数列,一次比较两个元素进行交换。

算法描述:
1、比较相邻的两个元素,若第一个比第二个大,则交换;
2、对每一对相邻元素均进行比较,最后的元素是最大数;
3、每次进行对比的元素均减少一个(最后的元素是已排好序的)。

代码实现
需要两个嵌套的循环,外层循环移动游标,内层循环遍历游标及之后的元素。通过两两交换,内循环结束循环位置排序正确,内循环结束交由外循环往后移动游标,开始新一轮内循环。

public static void bubbleSort(int[] arr){for (int i = arr.length; i > 0; i--) {      //外循环移动游标for(int j = 0; j < i && (j+1) < i; j++){    //内循环遍历游标及之后元素if(arr[j] > arr[j+1]){int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;System.out.println("Sorting: " + Arrays.toString(arr));}}}
}

复杂度分析

六、快速排序(快排Quick Sort)

基本思想
对冒泡法的改进,接用了分治策略。

首先选定枢纽元,或轴值pivot,通过一次排序,将待排序序列分为两部分:一部分均小于pivot,另一部分均大于pivot(等于的情况)

算法描述:

1、在数列中选中枢纽元pivot;
2、将小于枢纽元的元素放在pivot前面,大于枢纽元的元素放在pivot后面,此时pivot位于数列中间,称为分区操作。
3、递归的将小于pivot的子数列和大于pivot的子数列排序。

递归代码实现

public static <Anytype extends Comparable<? super Anytype>> quickSort(Anytype[] arr, int low, int high){if(arr.length <= 0) return;if(low >= high) return;int left = low;int right = high;int temp = median3(arr, left, right);   //通过三数中值分割法确定pivotwhile (left < right){while( arr[--right] >= temp){  //从后向前,若元素大于pivot则掠过        }while( arr[++left] <= temp){   //从前往后,若元素小于pivot则掠过        }if(left < right){//当left和right交错时,不在交换//否则就将两个元素互换swapReference(arr,left,right);}}//最终将枢纽元和left交换,此时的i是大于pivot的,所以交换后,i指向枢纽元,前面均小于枢纽元,后面均大于枢纽元swapReference(arr,left,high-1);quickSort(arr, low, left-1);quickSort(arr, left+1, high);
}

非递归代码实现

递归的本质是栈。借助栈保存中间变量实现递归。中间变量是指通过P…函数划分区间之后分成左右两部分的首尾指针。

public static void quickSortByStack(int[] arr){if(arr.length <= 0) return;Stack<Integer> stack = new Stack<Integer>();//初始状态的左右指针入栈stack.push(0);stack.push(arr.length - 1);while(!stack.isEmpty()){int high = stack.pop();     //出栈进行划分int low = stack.pop();int pivotIdx = partition(arr, low, high);//淇濆瓨涓棿鍙橀噺if(pivotIdx > low) {stack.push(low);stack.push(pivotIdx - 1);}if(pivotIdx < high && pivotIdx >= 0){stack.push(pivotIdx + 1);stack.push(high);}}
}private static int partition(int[] arr, int low, int high){if(arr.length <= 0) return -1;if(low >= high) return -1;int l = low;int r = high;int pivot = arr[l];    //坑1基准while(l < r){while(l < r && arr[r] >= pivot){  //坑2:从后向前找到比基准小的元素,插入到坑1r--;}arr[l] = arr[r];while(l < r && arr[l] <= pivot){   //坑3:从前往后找到比基准大的元素,插入到坑2l++;}arr[r] = arr[l];}arr[l] = pivot;   //基准填到坑3中,准备分治递归快排return l;
}

快排在同数量级(O(nlogn)的排序中平均性能最好。

复杂度

七、归并排序

基本思想
将两个或更多有序表合并为一个新的有序表。即先将待排序序列分为若干个子序列,对每个子序列进行排序,再将有序子序列合并为整体。

算法描述

1、自上而下的递归


2、自下而上的迭代

代码实现

包括两部分:
1、分解:将序列折半拆分;
2、合并:将划分后的序列段两两排序合并。

如何合并?
对于两段已经排序好的序列,首先比较两个序列的第一个元素,将较小值赋给第一个元素,再将较大值与另外一个序列的第二个元素比较,以此类推。

递归算法实现:

public static int[] mergingSort(int[] arr){if(arr.length <= 1) return arr;int num = arr.length >> 1;int[] leftArr = Arrays.copyOfRange(arr, 0, num);int[] rightArr = Arrays.copyOfRange(arr, num, arr.length);return mergeTwoArray(mergingSort(leftArr), mergingSort(rightArr));      //不断拆分为最小单元,再排序合并,
}private static int[] mergeTwoArray(int[] arr1, int[] arr2){int i = 0, j = 0, k = 0;int[] result = new int[arr1.length + arr2.length];  //申请额外的空间存储合并之后的数组while(i < arr1.length && j < arr2.length){      //选取两个序列中的较小值放入新数组if(arr1[i] <= arr2[j]){result[k++] = arr1[i++];}else{result[k++] = arr2[j++];}}while(i < arr1.length){     //若序列2都已经放入新数组,则将序列1中的多余元素移入新数组result[k++] = arr/1[i++];}while(j < arr2.length){     //若序列1都已经放入新数组,则将序列2中的多余元素移入新数组result[k++] = arr2[j++];}return result;
}

通过自上而下的递归实现的归并排序,存在堆栈溢出的风险

复杂度分析

拆分数组O(logn),合并数组O(n),综合时间复杂度为O(nlogn)0,拆分的子数组需要内存空间,空间复杂度较高为O(n)

八、基数排序(Radix Sort)

是一种非比较型整数排序算法。原理是将整数按位数切割成不同数字,按位数进行比较。 由于整数也可以表达字符串和特定格式的浮点数,所以也可以适用于其他格式。

基本思想:

将所有待比较数值统一为同样的数位长度,数位较短的前面补零,从最低位开始,依次进行一次排序,直到最高位比较结束。

两种方案:
MSD:从左/侧高位开始进行排序。适用于位数多的序列

LSD:从右侧低位开始进行排序。适用于位数少的序列。

算法描述:

1、取得数组中的最大数,并取得位数;
2、arr为原始数组,从最低位开始取每个位组成radix数组;
3、对radix进行排序

代码实现:
分配和收集

1、分配:将L[i]中的元素取出,首先确定其个位数上的数字,根据数字将其分配到与之序号相同的
2、收集:分配好后,再按照顺序依次将桶中的元素收集形成新的序列,对新序列重新执行分配和执行,直到运行完最高位,排序结束。

public static void radixSort(int[] arr){if(arr.length <= 1) return;//取得数组中的最大数,从而确定位数int max = 0;for(int i = 0; i < arr.length; i++){if(max < arr[i]){max = arr[i];}}//计算位数int maxDigit = 1;while(max / 10 > 0){maxDigit++;max = max / 10;}//申请一个桶空间,二维数组,其中10代表0-9int[][] buckets = new int[10][arr.length];int base = 10;//从低位到高位,对每一位遍历,将所有元素分配到桶中for(int i = 0; i < maxDigit; i++){int[] bktLen = new int[10];        //存储各个桶中存储的元素的个数//分配,将所有元素分配到桶中for(int j = 0; j < arr.length; j++){int whichBucket = (arr[j] % base) / (base / 10);buckets[whichBucket][bktLen[whichBucket]] = arr[j];bktLen[whichBucket]++;}//收集:将不同桶里的数据挨个取出来,为高一位排序做准备,从桶底开始取int k = 0;for(int b = 0; b < buckets.length; b++){for(int p = 0; p < bktLen[b]; p++){arr[k++] = buckets[b][p];}}base *= 10;}
}

复杂度分析

d为位数,r为基数,n为原数组个数。基数排序更适用于对时间、字符串等整体权值未知的数据进行排序。
此算法不改变相同元素之间的位置,所以是稳定的排序算法。

PS:应用到桶的概念的排序算法

1、基数排序:根据键值的每位数字来分配桶
2、计数排序:每个桶只存储单一键值
3、桶排序:每个桶只存储一定范围的数值

排序算法复杂度对比

基数排序时间复杂度最小,为什么没有快排、堆排序流行?

1、基数排序只适用于有基数的情况,而基于比较的算法适用范围更广。
2、内存上的考虑。作为一种通用算法,最好不要带来额外的内存开销。

时间复杂度极限
1、计数排序O(k+n):被排序的数是0-k范围内的整数;
2、基数排序O(d(k+n)):d位数,每个数位有k个取值;
3、桶排序O(n):被排序数在某个范围内,并且服从均匀分布。

说明:
1、当原表有序或基本有序,直接插入法和冒泡排序可以大大减少比较次数和移动次数,时间复杂度可将为O(n);但是快速排序法用于有序序列时,会退化成冒泡法,时间复杂度为o(n2)

2、原表是否有序,对简单选择排序、堆排序、归并排序、基数排序的时间复杂度影响不大

数据结构与算法分析——排序算法总结相关推荐

  1. [ 数据结构 -- 手撕排序算法第三篇 ] 希尔排序

    手撕排序算法系列之:希尔排序. 从本篇文章开始,我会介绍并分析常见的几种排序,大致包括插入排序,冒泡排序,希尔排序,选择排序,堆排序,快速排序,归并排序等. 大家可以点击此链接阅读其他排序算法:排序算 ...

  2. Java数据结构第一讲-排序算法

    常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...

  3. 数据结构和常用排序算法复杂度

    1.顺序表 插入操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数n/2 删除操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数(n-1)/2 按值 ...

  4. Java数据结构之八大排序算法

    目录 一.排序算法的介绍 1.排序算法 2.算法时间的频度 时间频度 3.时间复杂度 4.常见的时间复杂度 5.平均时间复杂度和最坏时间复杂度 6.空间复杂度 二.冒泡排序 1.基本介绍 2.模拟冒泡 ...

  5. 数据结构与算法分析 ——回溯算法之收费公路重建问题

    数据结构与算法分析第十章回溯算法之收费公路重建问题 一.  问题描述: 设给定N个点p1, p2,-,pn,它们位于x-轴上.xi是pi点的x坐标.这N个点确定了N(N-1)/2个点间距离.显然,如果 ...

  6. 值得收藏的时间复杂度速查表:数据结构操作、排序算法、图操作、堆操作

    时间复杂度速查表 这篇文章覆盖了计算机科学里面常见算法的时间和空间的大 OBig-O 复杂度. 在参加面试前,我们经常需要花费很多时间从互联网上查找各种搜索和排序算法的优劣,了节省大家的时间,我收集了 ...

  7. 数据结构-常用的排序算法

    总第123篇 好久不见哈,我终于又更新了,惊不惊喜,意不意外,哈哈哈哈.等之后会专门写一篇文章给大家汇报汇报我最近在忙什么呢,今天这篇还是接着之前的数据结构系列继续,主要讲讲数据结构里面常用的几种排序 ...

  8. 数据结构的六大排序算法详解

    文章目录 一.简单排序 1.Comparable接口介绍 2.冒泡排序 3.选择排序 4.插入排序 二.高级排序 1.希尔排序 2.归并排序 3.快速排序 4.排序的稳定性 一.简单排序 在我们的程序 ...

  9. 数据结构基础和排序算法

    数据结构和算法 1. 数据结构 1.1 稀疏数组 这个简单 稀疏数组即二维数组中有大量为0或同一个无效值的时候,将其压缩为只有有效数据的稀疏数组,需要使用时将其读写出来转为二维数组. public c ...

最新文章

  1. 高校分配男朋友?当专业第一遇到了专业第一......
  2. AFNetworking 2.0使用(持续更新)
  3. 修改docker镜像的存储地址的方法(--graph)
  4. boost::geometry::model::segment用法的测试程序
  5. 杭电oj2047-2049、2051-2053、2056、2058
  6. 双向链表中基本函数的实现
  7. Linux基础,命令的使用以及环境的安装,jdk,mysql,tomcat
  8. [USACO13MAR]Poker Hands【贪心】
  9. linux中^]是如何输出的
  10. 本地 hosts 文件找不到怎么办
  11. 水仙花数(python)
  12. ResponseEntity返回图片,下载图片
  13. 写一首春天去野外写生的诗
  14. 没有捷径!没有捷径!没有捷径!
  15. uniCloud使用
  16. 信捷XD系列PLC程序远程上下载怎么做?
  17. QQ2009无法显示好友自定义头像
  18. composer报错:Script @php think service:discover handling the post-autoload-dump event returned...解决
  19. java怎么把button放在最右侧,将一个button放左边另一个放右边
  20. linux 邮件客户端 outlook,如何在Ubuntu下使用类似outlook收发exchange的邮件

热门文章

  1. 百花齐放的家居行业联盟,三翼鸟率先撬动三个赛点
  2. 浅谈 SurfaceView、TextureView、GLSurfaceView、SurfaceTexture
  3. Fortran 双冒号的作用
  4. 什么是内联元素(行内)
  5. 带大家写一波微信公众号的爬取
  6. 【蓝桥杯】Python实现蛇行矩阵
  7. 一粒沙子一个世界(英特尔2008春季IDF)
  8. 硬盘常用分区格式与最大支持
  9. 交换机交换容量与包转发率的关系
  10. Ansible 系列之 Galaxy 工具