排序过程详细的动态图可参考https://www.cnblogs.com/onepixel/articles/7674659.htmlhtml
1.插入排序 稳定O(n^2)java
稳定的意思是a=b,本来a在b前面,排序完成后a也在b前面。shell
插入排序的思路就是将数组逻辑上分红两段,一段是排好序的,一段是未排序的,每次从未排序的段中取出来一个,去排好序的里面找位置插入。数组
找的过程将排好序的那段位置进行移动,这样就一直有个位置能够放当前这个元素。ui
开始认为第一个元素是排好序的。spa
public static void insertSort(int[] arr) { int length = arr.length; for (int i = 1; i < length; i++) { int data = arr[i]; for (int j = i - 1; j >= 0; j--) { //反向遍历比较 if (arr[j] > data) { arr[j + 1] = arr[j]; //位置后移 arr[j] = data; } else { break; // 前面都是排好序的,有一个比它小的,再前面确定更小,直接能够跳出,不用再比较 } } } }
2.希尔排序 不稳定 n*log2n-O(n^2)指针
希尔排序实际上是插入排序的一个改进版本,如上插入排序的代码,咱们发现有一个break的操做。遇到前面已经排好序了。咱们就不用再去遍历了。希尔排序就是增长这种有序段的数量。code
希尔排序是按下标增量进行分组,对每一组进行插入排序,随着下标增量的减小,数组中有序段也愈来愈多。最后下标增量到1的时候即是整组排序了。htm
{1,5,8,9,6,3}blog
若是按下标增量2那么就是1,8,6一组进行排序,5,9,3 一组进行排序
第一次:{1,3,6,5,8,9}
...增量依次递减直到1
public static void shellSort(int[] arr) { int len = arr.length; //增量每次折半,最后增量为1的时候就是最后一次排序 for (int increment = len / 2; increment >= 1; increment /= 2) { //相似插入排序 for (int i = increment; i < len; i++) { //按增量分开使用插入排序 int data = arr[i]; for (int j = i - increment; j >= 0 ; j -= increment) { if (arr[j] > data) { arr[j + increment] = arr[j]; //位置后移 arr[j] = data; } else { break; // 前面都是排好序的,有一个比它小的,再前面确定更小,直接能够跳出,不用再比较 } } } } }
3.冒泡排序 稳定 O(n^2)
依次比较相邻两个元素大小,进行交换,每次就能够选择最大或者最小的。
/** * 冒泡排序 稳定 依次比较相邻的两个元素大小,每轮结束就能够选出来一个最大或者最小的 * @param arr */ public static void bubbleSort(int[] arr) { for (int i = 0; i < arr.length; i++) { //外层循环控制轮次 for (int j = 0; j < arr.length - 1 - i; j++) { //内层循环次数愈来愈少,由于内层跑完一次就找到一个数 if (arr[j] > arr[j+1]) { //交换位置 arr[j] = arr[j] + arr[j+1]; arr[j+1] = arr[j] - arr[j+1]; arr[j] = arr[j] - arr[j+1]; } } } }
4. 选择排序 不稳定 O(n^2)
有点相似插入排序会将数组分红两段,去未排序段中每次选择中最大或者最小的到另外一段中。
好比从最左边开始,去依次遍历,找到最大或者最小的索引下标,而后交换位置。这样就找到了最大/最小的数。每轮只换一次位置,冒泡是每次比较均可能会换位置。
/** * 选择排序 不稳定 先拿出一个数,去依次遍历,找到最大或者最小的。每轮换一次位置,冒泡是每次比较均可能会换位置。 * @param arr */ public static void selectionSort(int[] arr) { int tempIndex; for (int i = 0; i < arr.length; i++) { tempIndex = i; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[tempIndex]) { //比较大小 记录下标位置 tempIndex = j; } } if (tempIndex != i) { //交换位置 arr[i] = arr[i] + arr[tempIndex]; arr[tempIndex] = arr[i] - arr[tempIndex]; arr[i] = arr[i] - arr[tempIndex]; } } }
5. 归并排序 稳定 O(nlogn)
归并排序是采用分治的思想,将要排序的数据拆分,再合并,过程以下图,合并的时候以下涂鸦线条,第一次红色线条,6和5比,5下去,左边的指针位置后移一位到9,第二次蓝色线条,6和9比,6下去,右边指针后移一位到8,第三次8和9比,8下去,最后剩下的9直接移下去。JDK的排序就是使用的归并排序java.util.Arrays#mergeSort()
代码
/** * 归并排序 JDK源码java.util.Arrays#mergeSort() * @param arr * @param left * @param right */ public static void mergeSort(int[] arr, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(arr, left, mid); //拆了以后的左 mergeSort(arr, mid + 1, right); //拆了以后的右 merge(arr, left, mid, right); //合并 } } /** * 归并 */ private static void merge(int[] arr, int left, int mid, int right) { int[] arrTemp = new int[arr.length]; int leftPoint = left; //左边第一个下标 int rightPoint = mid + 1; //右边第一个下标 int currentPoint = left; //当前下标 //左右两边的数据对于本身来讲都已是有序的,因此就拿右边的依次和左边的进行大小比较,将对应的大小关系放入临时数组对应下标 //左边指针走到mid位置或者右边指针走到right位置,既结束循环,两边剩下的有序数据,直接追加在临时数组后面,先左后右 while (leftPoint <= mid && rightPoint <= right) { if (arr[leftPoint] < arr[rightPoint]) { arrTemp[currentPoint] = arr[leftPoint]; leftPoint++; } else { arrTemp[currentPoint] = arr[rightPoint]; rightPoint++; } currentPoint++; } //先处理左边剩下的直接放入对应位置 while (leftPoint <= mid) { arrTemp[currentPoint++] = arr[leftPoint++]; } //再处理右边剩下的 while (rightPoint <= right) { arrTemp[currentPoint++] = arr[rightPoint++]; } //将临时数组里面的数据放入原始数组中,注意只放merge的那一段的数据 for (int i = left; i <= right; i++) { arr[i] = arrTemp[i]; } }
6.快速排序 不稳定 O(nlogn)
须要先选择一个基准数,通常选择第一个,和右边最后一个数据比较,若是比基准数小就交换位置,若是大则不交换,下标递减比较前一个,直到交换为止。而后切换成和左边第一个比较,若是比基准数大就换,不然下标递增继续判断,直到交换为止,而后切换到右边。。。就这样左右左右左右,直到碰到一块儿结束。次轮下来基准数左边是比它小的,右边是比它大的;将其左右的数据各自选一个基准数,执行同样的操做,如此反复,拆到最后就是一个有序的序列了。
如上图,最后分完就已是一个排好的序列了,快排是边拆边排,归并是先拆,合并的时候再排。由上逻辑能够看出,快速排序中基准数的大小是相当重要的,最好的结果就是每次都取偏中间的数做为基准数。咱们能够多随机选出来几个数,再从其中选一个出来做为基准数。和归并排序相比快排没有数据合并的过程,从下面的过程当中也能够看出来快排就是以前递归里面说到的尾递归
/** * 快速排序 * @param arr */ private static void quicklySort(int[] arr, int left, int right) { int base = arr[left]; //咱们选第一个做为基准数,也就是每次的进来left下标 int leftPoint = left; //左边找的位置 int rightPoint = right; //右边找的位置 while (leftPoint < rightPoint) { //左边指针小于右边 说明还没碰到一块儿 //从后往前找 若是后面的数大于等于基准数不交换,指针前移,须要的交换的时候跳出循环 while (leftPoint < rightPoint && arr[rightPoint] >= base) { rightPoint--; } //上述循环跳出,而且左边指针小于右边指针 说明须要交换数据,左边由于换一个小的数据过去因此指针后移 if (leftPoint < rightPoint) { int temp = arr[rightPoint]; arr[rightPoint] = arr[leftPoint]; arr[leftPoint] = temp; leftPoint++; } //切换 从前日后找 若是左边的数小于等于基准数不交换,指针后移,须要的交换的时候跳出循环 while (leftPoint < rightPoint && arr[leftPoint] <= base) { leftPoint++; } //上述循环跳出,而且左边指针小于右边指针 说明须要交换数据,右边由于换了一个大的数据过去因此指针前移 if (leftPoint < rightPoint) { int temp = arr[rightPoint]; arr[rightPoint] = arr[leftPoint]; arr[leftPoint] = temp; rightPoint--; } } if (left < leftPoint) { //左边 quicklySort(arr, left, leftPoint - 1); } if (leftPoint < right) { //右边 quicklySort(arr, leftPoint + 1, right); } }