排序算法
如下排序算法一共有八种,除了技术排序暂不考虑
常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)
- 插入排序:顾名思义,就是将一个个数往已经排序好了的数列中插入。
- 直接插入排序、
- 二分法插入排序、
- 希尔排序
- 交换排序:就是每次拿两个数按照一定的规则进行比较,然后交换位置
- 冒泡排序、
- 快速排序
- 选择排序:就是根据要求每次遍历一遍就选择一个数
- 直接选择排序、
- 堆排序
- 归并排序:就是将全部待排序的数列分来,通过递归分开无数个子集,最小为一个数,然后将每两个排列好的子集合并成一个集,也是递归实现
直接插入排序(内部排序、O(n2)、稳定):
就是从position=1的位置开始,往前面一个排序好了的队列中进行插入。
public class DirectInsertSort { public static void main(String[] args){ Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } System.out.println(); for(int i =1;i<a.length;i++){ //temp為待插的數 int temp =a[i]; //j為記錄下temp插入的位置 int j; for(j=i-1;j>=0;j--){ //每次將比temp大的數往后移一位 if(a[j]>temp){ a[j+1] = a[j]; } else{ //防止j往下走, break; } } a[j+1]=temp; } System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } }
二分插入排序:
希尔排序:(不稳定、O(nlogn))
冒泡排序:
就是每次遍历将最小的数冒出来,最终进行n次遍历,选出序列
public class MaoPaoSort { public static void main(String[] args) { Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } System.out.println(); for(int i =0;i<a.length;i++){ for(int j =0;j<a.length-1-i;j++){ int temp; if(a[j]>a[j+1]){ temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } }
快速排序(nlogn):
public class QuickSort { public static void main(String[] args) { Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } quicksort(a, 0, a.length-1); System.out.println(); System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } private static void quicksort(int []num,int start,int end){ int i = start; int j = end; int base = num[i]; if(start>=end) return; while(i!=j){ while(num[j]>=base && j>i){ j--; } if(i<j){ num[i]=num[j]; //比忘了 i++; } while(num[i]<=base && i<j){ i++; } if(i<j){ num[j]=num[i]; //別忘了 j--; } } num[i]=base; quicksort(num,start,i-1); quicksort(num,i+1,end); } }
时间复杂度计算:https://blog.csdn.net/not_in_mountain/article/details/77976743
直接选择排序:O(n2)
与直接插入排序正好相反,选择排序是从待排序的数中选出最小的放在已经排好的后面,这个算法选数耗时
public class DirectSelectSort { public static void main(String[] args) { Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } method(a); System.out.println(); System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } private static void method(int []num){ for(int i =0;i<num.length;i++){ int min = num[i]; int index= i; int temp;//用來交換的中間值 for(int j = i+1;j<num.length;j++){ if(num[j]<min){ min=num[j]; index=j; } } num[index]=num[i]; num[i]=min; } } }
堆排序(nlgn):
(1)完全二叉树——只有最下面的两层结点度小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树;
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶结点都处在最底层的二叉树。
堆的话,大家只要记得堆是一个完全二叉树(什么是完全二叉树,请不懂的读者去查资料),堆排序分为两种堆,大顶堆和小顶堆,大顶堆的意思就是堆顶元素是整个堆中最大的,小顶堆的意思就是堆顶元素是整个堆中最小的,满足:任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。堆排序是一个相对难理解的过程,下面我会较为清楚、详细的讲解一下堆排序。堆排序分为三个过程:
建堆:从一个数组顺序读取元素,建立一个堆(完全二叉树)
初始化:将堆进行调整,使得堆顶为最大(最大堆)或者最小(最小堆)的元素
维护:将堆顶元素出堆后,需要将堆的最后一个节点补充到堆顶,因为这样破坏了堆的秩序,所以需要进行维护。下面我们图示一下:
一般情况,建堆和初始化同步进行,
最后为如下所示,即为建堆、初始化成功。
我们可以观察下这个最大堆,看出堆顶是整个堆中最大的元素,而且除叶子节点外每个节点都大于其子节点。下面的过程就是当我们输出堆顶元素后,对堆进行维护。
过程是这样:将堆顶元素出堆后,用最后一个元素补充堆顶元素,这样破坏了之前的秩序,需要重新维护堆,在堆顶元素的左右节点中选出较小的和堆顶互换,然后一直递归下去,所以每次出一个元素,需要一次维护,堆排序适合解决topK问题,能将复杂度降到nlogK。
public class DuiSort { public static void main(String[] args) { Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } heapSort(a); System.out.println(); System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } public static void heapSort(int[] array) { if (array == null || array.length <= 1) { return; } buildMaxHeap(array); for (int i = array.length - 1; i >= 1; i--) { exchangeElements(array, 0, i); maxHeap(array, i, 0); } } private static void buildMaxHeap(int[] array) { if (array == null || array.length <= 1) { return; } int half = array.length / 2; for (int i = half; i >= 0; i--) { maxHeap(array, array.length, i); } } private static void maxHeap(int[] array, int heapSize, int index) { int left = index * 2 + 1; int right = index * 2 + 2; int largest = index; if (left < heapSize && array[left] > array[index]) { largest = left; } if (right < heapSize && array[right] > array[largest]) { largest = right; } if (index != largest) { exchangeElements(array, index, largest); maxHeap(array, heapSize, largest); } } public static void exchangeElements(int[] array, int index1, int index2) { int temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } }
/** * 选择排序-堆排序 * @param array 待排序数组 * @return 已排序数组 */ public static int[] heapSort(int[] array) { //这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1 for (int i = array.length / 2 - 1; i >= 0; i--) { adjustHeap(array, i, array.length); //调整堆 } // 上述逻辑,建堆结束 // 下面,开始排序逻辑 for (int j = array.length - 1; j > 0; j--) { // 元素交换,作用是去掉大顶堆 // 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置 swap(array, 0, j); // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。 // 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因 // 而这里,实质上是自上而下,自左向右进行调整的 adjustHeap(array, 0, j); } return array; } /** * 整个堆排序最关键的地方 * @param array 待组堆 * @param i 起始结点 * @param length 堆的长度 */ public static void adjustHeap(int[] array, int i, int length) { // 先把当前元素取出来,因为当前元素可能要一直移动 int temp = array[i]; for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树 // 让k先指向子节点中最大的节点 if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树 k++; } //如果发现结点(左右子结点)大于根结点,则进行值的交换 if (array[k] > temp) { swap(array, i, k); // 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断 i = k; } else { //不用交换,直接终止循环 break; } } } /** * 交换元素 * @param arr * @param a 元素的下标 * @param b 元素的下标 */ public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }
归并排序(nlgn):
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
public class SortTest { // 将有序数组a[]和b[]合并到c[]中 static void MemeryArray(int a[], int n, int b[], int m, int c[]) { int i, j, k; i = j = k = 0; while (i < n && j < m) { if (a[i] < b[j]) c[k++] = a[i++]; else c[k++] = b[j++]; } while (i < n) c[k++] = a[i++]; while (j < m) c[k++] = b[j++]; } public static void main(String[] args) { int a[] = { 2, 7, 8, 10, 299 }; int b[] = { 5, 9, 14, 20, 66, 88, 92 }; int c[] = new int[a.length + b.length]; MemeryArray(a, 5, b, 7, c); print(c); } private static void print(int[] c) { for (int i = 0; i < c.length; i++) { System.out.print(c[i] + " "); } } }
以看出合并有序数列的效率是比较高的,可以达到O(n)。解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。下面是归并排序代码:
public class GuibinSort { public static void main(String[] args) { Random random = new Random(); int a[] = new int[13]; for (int i = 0; i < a.length; i++) { a[i] = random.nextInt(100) + 1; } System.out.print("排序前:"); for(int i =0;i<a.length;i++){ System.out.print(a[i]+","); } //归并排序 method(a,a.length); System.out.println(); System.out.print("排序后:"); for(int k= 0;k<a.length;k++){ System.out.print(a[k]+","); } } private static boolean method(int []num,int length){ int p[] = new int [length]; mergeSort(num, 0, length-1, p); return true; } private static void mergeSort(int []num,int start,int end,int [] use_tosave){ if(start<end){ int mid=(start+end)/2; mergeSort(num, start, mid, use_tosave); mergeSort(num, mid+1, end, use_tosave); mergeArray(num, start, mid, end, use_tosave); } } private static void mergeArray(int num[],int start,int mid,int end,int [] use_tosave){ int i=start,j=mid; int m=mid+1,n=end; int k=0; while(i<=j && m<=n){ if(num[i]<=num[m]){ use_tosave[k++]=num[i++]; } else{ use_tosave[k++]=num[m++]; } } while(i<=j){ use_tosave[k++]=num[i++]; } while(m<=n){ use_tosave[k++]=num[m++]; } for(i=0;i<k;i++){ num[start+i]=use_tosave[i]; } } }
int guiBinDataList[] = new int[100]; public void guiBinMethod(){ guiBinMerge(0,guiBinDataList.length-1); } public void guiBinMerge(int start, int end){ if(start < end){ int mid = end/start; guiBinMerge(start, mid); guiBinMerge(mid+1,end); guiBinMergeSortedListMethod(start,mid,end); } } public int[] guiBinMergeSortedListMethod(int startIndex, int midIndex, int endIndex){ int[] output = new int[endIndex-startIndex+1]; int outIndex = 0,tempOne = startIndex,tempTwo = midIndex; while (tempOne <= midIndex && tempTwo <= endIndex){ if(guiBinDataList[tempOne] < guiBinDataList[tempTwo]){ output[outIndex++] = guiBinDataList[tempOne]; }else{ output[outIndex++] = guiBinDataList[tempTwo]; } } while (tempOne<=midIndex){ output[outIndex++] = guiBinDataList[tempOne++]; } while (tempTwo<=endIndex){ output[outIndex++] = guiBinDataList[tempTwo++]; } for (int k =0; k<=outIndex; k++){ guiBinDataList[startIndex+k] = output[k]; } return guiBinDataList; }