大家好,欢迎来到IT知识分享网。
常见的排序算法:
按照插入排序分:1.直接插入排序 2.希尔排序
按照选择排序分:1.选择排序 2.堆排序
按照交换排序分:1.冒泡排序 2.快速排序
按照归并排序分:1.归并排序
常见的排序思想:是使用双指针或者三指针,在同一个数组上遍历、比较、交换。
1、直接插入排序
类比于扑克牌抽排,双指针思路。
1.时间复杂度:最坏情况下O(N^2),如54321,最好情况下O(N),如12345(只用i动,j不用走)
2.空间复杂度:O(1)
3.稳定性:稳定
4.使用场景:元素集合越接近有序,直接插入排序算法时间效率越高。
public void insertSort(int[] array){ //[27,15,9,18,28] for (int i = 1; i < array.length; i++) { int temp = array[i]; int j = i-1; for (; j >=0 ; j--) { if (array[j] > temp){ array[j+1] = array[j]; }else { //如果不用调整,则表示已经找到了该放的位置了 //因为前面都有序了 break; } } //当j循环走完以后,要么就是走完了所有,要么就是找到了最小位置 //所以走完以后直接把j+1的位置赋值为temp即可 array[j+1] = temp; } }
2、希尔排序(缩小增量排序)
基本思想:先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。当到达 =1时,所有记录在统一组内排好序。
时间复杂度:O(n^1.25)~O(1.6*n^1.25)
空间复杂度:O(1)
稳定性:不稳定
希尔排序的特性总结:
希尔排序是对直接插入排序的优化。
当
gap > 1
时都是预排序,目的是让数组更接近于有序。当
gap == 1
时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
希尔排序的时间复杂度不好计算,因为
gap
的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定。
跳着分的目的是让交换之后,大的数都在后面,小的数都在前面。前面的这些趟都是预排序,最后1趟才是真正的排序。每一组都是插入排序,越有序越快。
public void shellSort(int[] array){ int gap = array.length; //gap表示array数组,分为多少个组 while (gap > 1){ //每次缩短一半的分组 gap /= 2; shell(array,gap); } } / * 对每组进行插入排序 * @param array * @param gap */ public 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-=gap) { if (array[j] > tmp){ array[j+gap] = array[j]; }else { break; } } array[j+gap] = tmp; } }
3、选择排序
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
public void selectSort(int[] array){ //双指针思想 for (int i = 0; i < array.length; i++) { int miniIndex = i; for (int j = i+1; j < array.length; j++) { //如果j指向的数值小于最小值,那么就更新最小值索引 if (array[j] <= array[miniIndex]){ miniIndex = j; } } //找到最小值索引以后,就跟i位置交换 swap(array,i,miniIndex); } }
//3指针思想 public void doubleSelectSort(int[] array){ int left = 0; int right = array.length-1; while (left < right){ int minIndex = left; int maxIndex = left; //走一趟left和right之间的值 for (int i = left + 1;i<= right;i++){ if (array[i] < array[minIndex]){ minIndex = i; } if (array[i] > array[maxIndex]){ maxIndex = i; } } //注意,如果最大值在第一个位置,会出现bug //如果最大值在下标为0的位置,此时第一个swap会把最大值换到minIndex位置上 //而第二个swap会把0位置的最小值换到数组最后一个位置,就乱了 swap(array,minIndex,left); //如果最大值的索引,正好等于left //交换完成以后,left存储的是最小值,minIndex存储的是最大值 //所以需要多加一步,将minIndex赋给maxIndex if (maxIndex == left){ maxIndex = minIndex; } swap(array,maxIndex,right); left++; right--; } }
4、堆排序
利用堆的特性来排序。排升序是用大根堆,排降序是用小根堆。其实就是不断地将堆中的树进行向上升序或者向下排序,由于堆中的元素是大根堆或者小根堆排列的,所以最终的树就是有序的了。
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定
public void heapSort(int[] array){ //先把数组创建成堆 createHeap(array); //从最后一棵树开始排序 int end = array.length-1; while (end > 0){ swap(array,0,end); siftDown(array,0,end); end--; } } private void siftDown(int[] array,int parent, int lenght) { int child = 2 * parent + 1; while (child < lenght){ if (child + 1<lenght && array[child] < array[child+1]){ child++; } if (array[child] > array[parent]){ swap(array,child,parent); parent = child; child = 2*parent+1; }else { break; } } } private void createHeap(int[] array) { //从最后一个父亲结点开始创建 for (int parent = (array.length-1-1)/2; parent >=0 ; parent--) { siftDown(array,parent,array.length); } }
5、冒泡排序
双指针
时间复杂度:O(n^2),优化以后最好情况下可以达到O(n)
空间复杂度:O(1)
稳定性:稳定
public void bubbleSort(int[] array){ for (int i = 0; i < array.length-1; i++) { boolean flg = false; for (int j = 0; j < array.length-1-i; j++) { if (array[i] > array[j]){ swap(array,i,j); flg = true; } } if (!flg){ break; } } }
6、快速排序
元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
。
时间复杂度:最好的情况下,O(N*logN),当最坏的情况下,O(N^2),逆序或者有序
空间复杂度:O(logN)
稳定性:不稳定
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
其实最核心的任务就是找到相交点,交换。
6.1 Hoare法
递归以后层数多了可能会栈溢出。因为怕就怕他切分的这棵树,左边长右边短,或者左边短右边长。如果是均匀切分的话,其实是效果比较好的。
private void quick(int[] array,int start,int end){ if (start >= end){ return; } //每次相遇的位置 int pivot = partitionHoare(array,start,end); //以相遇的位置作为分割点,切分以后递归继续找 quick(array,start,pivot-1); quick(array,pivot+1,end); } private int partitionHoare(int[] array, int left, int right) { //待比较元素 int tmp = array[left]; int i =left; while (left < right){ //单独的循环,不能一直减到超过左边的边界 while (left < right && array[right] >= tmp){ right--; } while (left < right && array[left] <= tmp){ left ++; } //交换left和right找到的值 swap(array,left,right); } //此时left和right都在相遇点了,将相遇点换到最前面 swap(array,i,left); return left; }
6.2 挖坑法(Hoare衍生出来的)
挖坑法就是把基准从数组中拿出来,数组中就留下了一个坑,然后同样从右边和左边遍历,遇到大于或者小于基准的就把那个元素,拿到坑这个位置放下,反复循环,知道左右索引相遇,就把基准填入最后一个坑里。
private int partitionHole(int[] array,int left,int right){ //待比较元素 int tmp = array[left]; while (left < right){ //单独的循环,不能一直减到超过左边的边界 while (left < right && array[right] >= tmp){ right--; } //right找到比基准小的值,就把right所指向的元素填入left位置 //因为此时left位置就是坑 array[left] = array[right]; while (left < right && array[left] <= tmp){ left ++; } array[right] = array[left]; } array[left] = tmp; return left; }
值得注意的2个细节:
1,array[right] >= tmp 为什么要加等于?
如果不加等于,会死循环。因为left=right,left无法++right无法–,会不停地交换这两个值。
2,为什么从右边开始而不是先从左边开始?
如果先让left先走,那么走到相遇地方的时候,left指向的是一个比较大的数,此时将left和最左边交换,会让这个大的数换到最左边去。而如果是从右边先开始的话,right不断–,当遇到left的时候,right走到的是一个较小的数,此时再与最左边交换,就没问题。
6.3 前后指针法
cur用来指向比基准小的数,prev用来推着比基准大的数,cur不停地找比基准小的数,去和prev指向的比基准大的数交换。给人的感觉就是prev在不停地推着这些比基准大的数往前滚动,cur作为开路先锋去找小的数来交换。
//前后指针法1 private int partitionBFPoint1(int[] array, int left, int right) { int prev = left ; int cur = left+1; while (cur <= right) { if(array[cur] < array[left] && array[++prev] != array[cur]) { swap(array,cur,prev); } cur++; } swap(array,prev,left); return prev; }
//前后指针法2 private int partitionBFPoint2(int[] array, int left, int right) { int d = left + 1; int pivot = array[left]; for (int i = left + 1; i <= right; i++) { if (array[i] < pivot) { swap(array, i, d); d++; } } swap(array, d - 1, left); return d - 1; }
6.4 快速排序小结
三种快速排序的方法:
1.Hoare法
2.挖坑法
3.前后指针法
这三种方法每次划分以后的序列顺序,有可能是不一样的。
当面临一个序列,要你判断他用的哪一种快速排序的方法的时候。根据以往的经验,先后检验的顺序是挖坑法、Hoare法、前后指针法。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/124514.html