一,分类
主要的排序大致分为以下几类:
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)稳定性:不稳定