一,分类
主要的排序大致分为以下几类:
1,插入排序,又分为直接插入排序和希尔排序
2,选择排序,又分为选择排序和堆排序
3,交换排序,又分为冒泡排序和快速排序
4,归并排序
二,插入排序
1,直接插入排序
一个数组,定义两个变量i和j,i从数组的第二个元素开始往后遍历,直到数组结束。每次遍历把下标为i的值储存到临时变量tmp中。与此同时,j=i-1,j往前遍历,每一次下标j对应的值都与tmp进行比较,如果j的对应值大于tmp,则arr【j+1】=arr【j】,如果小于tmp的值,则直接跳出循环。因为如果遇到小于tmp的值,则这个值前面的数据肯定都小于tmp,这时就可以直接将tmp的值放入arr【j+1】里面。
public static void insertSort(int[] array){for (int i = 1; i < array.length; i++) {int tmp=array[i];int j = i-1;for (; j >=0 ; j--) {if (tmp<array[j]){array[j+1]=array[j];}else {break;}}array[j+1]=tmp;} }
简易图:
总结:
(1)对于直接插入排序,越有序,排序越快,所以如果一组数据趋于有序时,可以优先选择直接插入排序
(2)时间复杂度:O(n^2)
(3)空间复杂度:O(1)
(4)稳定性:稳定
注:稳定性指:
2,希尔排序
希尔排序时对直接插入排序的优化。其又称为缩小增量的排序,将数据分成不同的组,然后将每一组的数据进行直接插入排序,然后将组数不断减少,直到减少到一,每减少一次就排一次序。当组数多的时候每组的数据少,所以时间复杂度小,当组数小的时候,虽然数据比较多,但是数据是趋于有序的,所以直接插入排序的时间复杂度也较低,综上所述,这种方法的效率较高。
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 tmp=array[i];int j = i-gap;for (; j >=0 ; j=j-gap) {if (tmp<array[j]){array[j+gap]=array[j];}else {break;}}array[j+gap]=tmp;} }
总结:
(1) 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算
时间复杂度(估算):O(n*log2(n))
(2)空间复杂度:O(1)
(3)稳定性:不稳定
三,选择排序
1,选择排序
定义i和j两个变量,i从0开始遍历整个数组,定义最小值的下标变量为minIndex,当 i 每等于一个值时,minIndex=i,j就从i+1开始向后遍历,遇到array[minIndex]>array[j],就将minIndex=j从而保证这是这次 j 遍历的数据中的最小值,直到 j 遍历完整个数组后,将下标为i的值与下标为minIndex的值进行交换。从而使i的位置是这次 j 遍历的数据中的最小值。然后i++,继续寻找第二小的值放到 i 的位置……
下图为i=0时的一次遍历:
private static void swap(int[]array,int i,int j){int tmp=array[i];array[i]=array[j];array[j]=tmp; } public static void selectSort(int[]array){for (int i = 0; i < array.length; i++) {int minIndex=i;for (int j = i+1; j < array.length; j++) {if (array[minIndex]>array[j]){minIndex=j;}}swap(array,i,minIndex);} }
当然我们也可以在 j 遍历的时候同时找到最小值和最大值的下标,但需要注意的是,第一次遍历之后就可以筛选出最大值和最小值,这时最小值放第一个,最大值放最后一个,第二次遍历的时候就不需要遍历头和尾了。因此每次遍历就可以挑出一对数据,最大值和最小值。所以下一次遍历就只需要从中间寻找最大值和最小值了,所以i只需要遍历一半的数据就可以完成整个排序。
public static void selectSort2(int[]array){for (int i = 0; i < array.length/2; i++) {int minIndex= i;int maxIndex= i;for (int j = i+1; j < array.length-i; j++) {if (array[j]< array[minIndex]){minIndex=j;}if (array[j]> array[maxIndex]){maxIndex=j;}}swap(array,minIndex,i);if (maxIndex==0){maxIndex=minIndex;}swap(array,maxIndex,array.length-1-i);} }
总结:
(1) 时间复杂度:O(n^2)
(2)空间复杂度:O(1)
(3)稳定性:不稳定
2,堆排序
堆排序时首先要调整成大根堆,因为大根堆下标为0的元素一定是最大的,所以可以将第一个元素和最后一个下标的元素交换,然后将第一个元素向下调整重新调整为大根堆,调整的终点是前一次调整的数组长度-1(已经选出了最大的元素,现在需要在剩余的元素中找到第二大的,所以向下调整大范围不包括已经排好序的数据),然后然后循环上述行为。使数组中的每一个元素都得到调整从而就可以得到顺序了。
延伸说明:
调整大根堆:我们需要求出最后一个子节点的父节点,然后向前遍历,将每一个父节点就进行向下调整。
向下调整:需要知道需要调整的父节点的下标和调整的范围(如果是建立大根堆,调整的范围就是到最后一个下标),知道父亲节点后求出左子节点,然后判断是否有右子节点,如果有那么判断array[child+1]与array[child]的大小关系,如果关系是大于,那么child++,这样保证child所指向的是较大的子孩子。然后将较大的子孩子和父节点的值进行比较,如果孩子节点大于父亲节点,那么两者交换,因为如果两者交换的话,会影响被交换为子节点的节点作为父亲节点时,与它自己的子节点的大小关系,所以交换后要parent=child; child=parent*2+1;然后再次重复循环上述操作,直到孩子节点是越界,则代表调整完毕。但如果父亲节点大于孩子节点就可以直接跳出循环了,因为父子节点之间不需要交换,而子节点以下的节点本身就是调整好的所以不需要再次调整,直接跳出循环即可。
public static void siftDown(int []array,int parent,int end){int child=parent*2+1;while (child<end+1){if (child+1<=end){if (array[child+1]>array[child]){child++;}}if (array[child]>array[parent]){swap(array,child,parent);parent=child;child=parent*2+1;}else {break;}}} public static void createBigHeap(int[]array){int child=array.length-1;int parent= (child-1)/2;while (parent>=0){siftDown(array,parent,array.length-1);parent--;} }public static void heapSort(int []array){//调整为大根堆createBigHeap(array);int end= array.length-1;while (end>0){swap(array,0,end);end--;siftDown(array,0,end);} }
总结:
(1) 时间复杂度:O(n*log2(n))
(2)空间复杂度:O(1)
(3)稳定性:不稳定