简单的说就是一组数通过某个排序算法后仍然能保持他们在排序以前的相对次序就说这个排序方法是稳定的, 好比说,a1,a2,a3,a4四个数, 其中a2=a3,若是通过排序算法后的结果是 a1,a3,a2,a4咱们就说这个算法是非稳定的,若是仍是原来的顺序a1,a2,a3,a4,咱们就会这个算法是稳定的html
选择排序,顾名思义,在循环比较的过程当中确定存在着选择的操做, 算法的思路就是从第一个数开始选择,而后跟他后面的数挨个比较,只要后面的数比它还小,就交换二者的位置,而后再从第二个数开始重复这个过程,最终获得从小到大的有序数组java
算法实现:算法
public static void select(int [] arr){ // 选取多少个标记为最小的数,控制循环的次数 for (int i=0;i<arr.length-1;i++){ int minIndex = i; // 把当前遍历的数和它后面的数依次比较, 并记录下最小的数的下标 for (int j=i+1;j<arr.length;j++){ // 让标记为最小的数字,依次和它后面的数字比较,一旦后面有更小的,记录下标 if (arr[minIndex]>arr[j]){ // 记录的是下标,而不是着急直接交换值,由于后面可能还有更小的 minIndex=j; } } // 当前最小的数字下标不是一开始咱们标记出来的那个,交换位置 if (minIndex!=i){ int temp=arr[minIndex]; arr[minIndex]=arr[i]; arr[i]=temp; } } } }
时间复杂度: n + n-1 + n-2 + .. + 2 + 1 = n*(n+1)/2 = O(n^2)shell
稳定性: 好比: 5 8 5 2 7 通过一轮排序后的顺序是2 8 5 5 7, 原来两个5的先后顺序乱了,所以它不稳定数组
推荐的使用场景: n较小时安全
辅助存储: 就一个数组,结果是O(1)数据结构
见名知意,在排序的过程当中会存在插入的状况,依然是从小到大排序 算法的思路: 选取第二个位置为默认的标准位置,查看当前这个标准位置以前有没有比它还大的元素,若是有的话就经过插入的方式,交换二者的位置,怎么插入呢? 就是找一个中间变量记录当前这个标准位置的值,而后让这个标准位置前面的元素往前移动一个位置,这样如今的标准位置被新移动过来的元素给占了,可是前面空出来一个位置, 因而将这个存放标准值的中间元素赋值给这个空出来的位置,完成排序性能
代码实现:ui
/** * 思路: 从数组的第二个位置开始,选定为标准位置,这样开始的话,能够保证,从标准位置开始往前所有是有序的 * @param arr */ private static void insetSort(int[] arr) { int temp; // 从第二个开始遍历index=1 , 一共length 个数,一直循环到,最后一个数参加比较, 就是 1 <-> length 次 for (int i=1;i<arr.length;i++){ // 判断大小,要是如今的比上一个小的话,准备遍历当前位置之前的有序数组 if (arr[i] < arr[i-1]){ // 存放当前位置的值 temp=arr[i]; int j; // 循环遍历当前位置及之前的位置的有序数组,只要是从当前位置开始,前面的数比当前位置的数大,就把这个大的数替插入到当前的位置 // 随着j的减小,实际上每次循环都是前一个插到后一个的位置上 for (j=i-1;j>=0&&temp<arr[j];j--){ arr[j+1]=arr[j]; } // 直到找出一个数, 不比原来存储的那个当前的位置的大,就把存起来的数,插到这个数的前面 arr[j+1]=temp; } } }
时间复杂度:.net
稳定性: 从第2个位置开始遍历,标准位以前数组一直是有序的,因此它确定稳定
辅助存储: O(1)
推荐的使用场景: n大部分有序时
最熟悉的排序方式
代码实现:
private static void sort(int[] ints) { System.out.println("length == "+ints.length); // 控制比较多少轮 0- length-1 一共length个数,最坏比较lenth-1次 for (int i=0;i<ints.length-1;i++){ // 每次都从第一个开始比较,每次比较的时候,最多比较到上次比较的移动的最新的下标的位置,就是 length-0-i for (int j=0;j<ints.length-1-i;j++){ int temp; if (ints[j] > ints[j+1]) { temp=ints[j]; ints[j]=ints[j+1]; ints[j+1]=temp; } } } }
时间复杂度:
稳定性: 稳定
辅助存储空间: O(1)
推荐的使用场景: n越小越好
算法的思路: 先经过递归将一个完整的大数组[5,8,3,6]拆分红小数组, 通过递归做用,最终最小的数组就是[5],[8],[3],[6]
递归到最底层后就会有弹栈的操做,经过这时建立一个临时数组,将这些小数组中的数排好序放到这个临时数组中,再用临时数组中的数替换待排序数组中的内容,实现部分排序, 重复这个过程
/** * 为何须要low height 这种参数,而不是经过 arr数组计算出来呢? * --> 长个心,每当使用递归的时候,关于数组的这种信息写在参数位置上 * @param arr 须要排序的数组 * @param low 从哪里开始 * @param high 排到哪里结束 */ public static void mergeSort(int[] arr, int low, int high) { int middle = (high + low) / 2; if (low < high) { // 处理左边; mergeSort(arr, low, middle); // 处理右边 mergeSort(arr, middle + 1, high); // 归并 merge(arr, low, middle, high); } } /** * @param arr * @param low * @param middle * @param high */ public static void merge(int[] arr, int low, int middle, int high) { // 临时数组,存储归并后的数组 int[] temp = new int[high - low + 1]; // 第一个数组开始的下标 int i = low; // 第二个数组开始遍历的下标 int j = middle + 1; // 记录临时数组的下标 int index = 0; // 遍历两个数组归并 while (i <= middle && j <= high) { if (arr[i] < arr[j]) { temp[index] = arr[i]; i++; } else { // todo 在这里放个计数器++ , 能够计算得出 反序对的个数 (这样的好处就是时间的复杂度是 nlogn) temp[index] = arr[j]; j++; } index++; } while (j <= high) { temp[index] = arr[j]; j++; index++; } while (i <= middle) { temp[index] = arr[i]; i++; index++; } // 把临时入数组中的数据从新存原数组 for (int x = 0; x < temp.length; x++) { System.out.println("temp[x]== " + temp[x]); arr[x + low] = temp[x]; } }
按上面说的[5,8,3,6]来讲,通过递归他们会被分红这样
---------------压栈-------------------- 左 右 [5,8] [3,6] [5] [8] [3] [6] ------------下面的弹栈----------------- [5]和[8]归并,5<8 获得结果: [5,8] [3]和[6]归并, 3<6 获得结果 [3,6] 因而通过两轮归并就获得告终果 [5,8] [3,6] 继续归并: 建立一个临时数组 tmp[] 5>3 tmp[0]=3 5<6 tmp[1]=5 8>6 tmp[2]=6 tmp[3]=8 而后让tmp覆盖原数组获得最终结果
推荐使用的场景: n越大越好
时间复杂度: 最好,平均,最坏都是 O(nlogn) (这是基于比较的排序算法所能达到的最高境界)
稳定性能: 稳定
空间复杂度: 每两个有序序列的归并都须要一个临时数组来辅助,所以是 O(N)
是分支思想的体现, 算法的思路就是每次都选出一个标准值,目标是通过处理,让当前数组中,标准值左边的数都小于标准值, 标准值右边的数都大于标准值, 重复递归下去,最终就能获得有序数组
实现:
// 快速排序 public static void quickSort(int[] arr, int start, int end) { // 保证递归安全的进行 if (start < end) { // 1. 找一个中间变量,记录标准值,通常是数组的第一个,以这个标准的值为中心,把数组分红两部分 int strand = arr[start]; // 2. 记录最小的值和最大的值的下标 int low = start; int high = end; // 3. 循环比较,只要个人最开始的low下标,小于最大值的下标 就不停的比较 while (low < high) { System.out.println("high== " + high); // 从右边开始,若是右边的数字比标准值大,把下标往前动 while (low < high && strand <= arr[high]) { high--; } // 右边的最high的数字比 标准值小, 把当前的high位置的数字赋值给最low位置的数字 arr[low] = arr[high]; // 接着从作low 的位置的开始和标准值比较, 若是如今的low位置的数组比标准值小,下标日后动 while (low < high && arr[low] <= strand) { low++; } // 若是low位置的数字的比 标准值大,把当前的low位置的数字,赋值给high位置的数字 arr[high] = arr[low]; } // 把标准位置的数,给low位置的数 arr[low] = strand; // 开始递归,分开两个部分递归 // 右部分 quickSort(arr, start, low); // 左部分 quickSort(arr, low + 1, end); } }
推荐使用场景: n越大越好
时间复杂度:
稳定性: 快排是不稳定的
希尔排序其实能够理解成一种带步长的排序方式, 上面刚说了插入排序的实现方式,上面说咱们默认从数组的第二个位置开始算,实际上就是说步长是1,下标的移动每次都是1
对于希尔排序来讲,它默认的步长是 arr.length/2 , 每次步长都减小一半, 最终的步长也会是1
代码实现:
/** * 希尔排序,在插入排序的基础上,添加了步长 , * // todo 只要在本步长范围内,这些数字为组成一个组进行 插入排序 * 初始 步长=length/2 * 后来: 步长= 步长/2 * 直到最后: 步长=1; 正宗的插入排序 * @param ints */ private static void shellSort(int[] ints) { // 记录当前的步长 int step=ints.length/2; // 步长==》控制遍历几轮, 而且每次步长都是上一次的一半大小 for (int i=step;i>0;i/=2){ // 遍历当前步长下面的所有组,这里的j1, 就至关于插入排序中第一次开始的位置1前面的0下标 for (int j1=i;j1<ints.length;j1++){ // 遍历本组中所有元素 == > 从第二个位置开始遍历 // x 就至关于插入排序中第一次开始的位置1 for(int x=j1+i;x<=ints.length-1;x+=i){ // 从当前组的第二个元素开始,一旦发现它前面的元素比他小, // 插入排序, 1. 把当前的元素存起来 2. 循环它前面的元素往前移 3. 把当前元素插入到合适的位置 if(ints[x]<ints[x-i]){ int temp=ints[x]; int j ; for(j=x-i;j>=0&&ints[j]>temp;j=j-i) { ints[j+i]=ints[j]; } ints[j+i]=temp; } } } } }
空间复杂度: 和插入排序同样都是O(1)
稳定性: 希尔排序因为步长的缘由,而不向插入排序,一经开始标准位置前的数组即刻有序, 因此希尔排序是不稳定的
希尔排序的性能没法准确量化,跟输入的数据有很大关系在实际应用中也不会用它,由于十分不稳定,虽然比传统的插入排序快,但比快速排序等慢
其时间复杂度介于O(nlogn) 到 O(n^2) 之间
堆排序是借助了堆这种数据结构完成的排序方式,堆有大顶堆和小顶堆, 将数组转换成大顶堆而后进行排序的会结果是数组将从小到大排序,小叮当则相反
什么是堆呢? 堆实际上是能够当作一颗彻底二叉树的数组对象, 那什么是彻底二叉树呢? 好比说, 这颗数的深度是h,那么除了h层外, 其余的1~h1
层的节点都达到了最大的数量
算法的实现思路: 经过递归,将数组当作一个堆,从最后一个非叶子节点开始,将这个节点转换成大顶堆, 什么是大顶堆呢? 就是根节点老是大于它的两个子节点, 重复这个过程一直递归到堆的根节点(此时根节点是最大值),此时整个堆为大顶堆, 而后交换根节点和最后一个叶子节点的位置,将最大值保存起来
例: 假设待排序序列是a[] = {7, 1, 6, 5, 3, 2, 4},而且按大根堆方式完成排序
代码实现:
public static void sort(int[] ints) { // 开始位置是最后一个非叶子节点 int start = ints.length / 2-1; // 调整成大顶堆 for (int i = start; i >= 0; i--) { maxHeap(ints, ints.length, i); } // 调整第一个和最后一个数字, 在把剩下的转换为大定堆, j--实现了,再也不调整本轮选出的最大的数 for (int j = ints.length - 1; j > 0; j--) { int temp = ints[0]; ints[0] = ints[j]; ints[j] = temp; maxHeap(ints, j, 0); } } /** * 转换为大顶堆, 其实就是比较根节点和两个子节点的大小,调换他们的顺序使得根节点的值大于它的两个子节点 * * @param arr * @param size * @param index 从哪一个节点开始调整 (一开始转换为大顶堆时,使用的是最后一个非夜之节点, 可是转换完成以后,使用的就是0,从根节点开始调整) */ public static void maxHeap(int[] arr, int size, int index) { // 当前节点的左子节点 int leftNode = 2 * index + 1; // 当前节点的右子节点 int rightNode = 2 * index + 2; // 找出 当前节点和左右两个节点谁最大 int max = index; if (leftNode < size && arr[leftNode] > arr[max]) { max = leftNode; } if (rightNode < size && arr[rightNode] > arr[max]) { max = rightNode; } // 交换位置 if (max != index) { int temp = arr[index]; arr[index] = arr[max]; arr[max] = temp; // 交换位置后,可能破坏以前的平衡(跟节点比左右的节点小),递归 // 有可能会破坏以max为定点的子树的平衡 maxHeap(arr, size, max); } }
推荐的使用场景: n越大越好
时间复杂度: 堆排序的效率与快排、归并相同,都达到了基于比较的排序算法效率的峰值(时间复杂度为O(nlogn))
空间复杂度: O(1)
稳定性: 不稳定
图片来源: 点击进入连接
算法思路:
看上图中的绿色部分, 假设咱们有下标从0-9,一共10个桶
第一排是给咱们排序的一组数
咱们分别对取出第一排数的个位数,放入到对应下标中的桶中,再依次取出,就获得了第三行的结果, 再取出三行的十位数,放入到桶中,再取出,就获得最后一行的结果
// 基数排序 // 建立10个数组,索引从0-9 // 第一次按照个位排序 // 第二次按照十位排序 // 第三次按照百位排序 // 排序的次数,就是数组中最大的数的位数 public static void radixSort(int[] arr){ int max = Integer.MIN_VALUE; // 循环找到最大的数,控制比较的次数 for (int i : arr) { if (i>max){ max=i; } } System.out.println(" 最大值: "+max); // 求最大数字的位数,获取须要比较的轮数 int maxLength = (max+"").length(); System.out.println(" 最大串的长度: "+maxLength); // 建立应用建立临时数据的数组, 整个大数组中存放着10个小数组, 这10个小数组中真正存放的着元素 int [][] temp = new int[10][arr.length]; // 10个 长度为arr.length长度的数组 // todo 用于记录在temp中相应的数组中存放的数字的数量 int [] counts = new int[10]; // 还须要添加另外一个变量n 由于咱们每轮的排序是从的1 - 10 - 100 - ... 开始求莫排序 for(int i=0,n=1;i<maxLength;i++,n*=10){ // 计算每个数字的余数,遍历数组,将符合要求的放到指定的数组中 for (int j=0;j<arr.length;j++){ int x = arr[j]/n%10; // 把当前遍历到的数据放入到指定位置的二维数组中 temp[x][counts[x]] = arr[j]; // 更新二维数组中新更改的数组后的 新长度 counts[x]++; } int index =0; // 把存放进去的数据从新取出来 for (int y=0;y<counts.length;y++){ // 记录数量的那个数组的长度不为零,咱们才区取数据 if (counts[y]!=0){ // 循环取出元素 for (int z =0;z<counts[y];z++){ // 取出 arr[index++] = temp[y][z]; } // 把数量置为0 counts[y]=0; } } } }
稳定性: 稳定
时间复杂度: 平均、最好、最坏都为O(k*n),其中k为常数,n为元素个数
空间复杂度: O(n+k)
算法思路: 相对比较好想, 给咱们一组数,咱们在选出这组数中最大值和数组的length的长度,选最大的值当成新数组的长度,而后遍历旧的数组,将旧数组中的值放入到新数组中index=旧数组中的值的位置
而后一次遍历旧数组中的值,就能获得最终的结果
代码实现:
private static void sort(int[] ints) { int max = Integer.MIN_VALUE; for (int anInt : ints) { if (anInt>max) max=anInt; } int maxLength = ints.length-max >0 ? ints.length:max; int[] result = new int[maxLength]; for (int i=0;i<ints.length;i++){ result[ints[i]] +=1 ; } for(int i=0 ,index = 0;i<result.length;i++){ if (result[i]!=0){ for (int j=result[i];j>0;j--){ ints[index++] = i; } } } }
时间复杂度:
空间复杂度:O(n * k)
稳定性:稳定
典型的用空间换时间的算法
算法思路: 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
遍历待排序集合,将每个元素出现的次数记录到元素值对应的额外空间内;
对额外空间内数据进行计算,得出每个元素的正确位置;
将待排序集合每个元素移动到计算得出的正确位置上。
代码实现:
public static int[] sort(int[] A) { //一:求取最大值和最小值,计算中间数组的长度:中间数组是用来记录原始数据中每一个值出现的频率 int max = A[0], min = A[0]; for (int i : A) { if (i > max) { max = i; } if (i < min) { min = i; } } //二:有了最大值和最小值可以肯定中间数组的长度 //存储5-0+1 = 6 int[] pxA = new int[max - min + 1]; //三.循环遍历旧数组计数排序: 就是统计原始数组值出现的频率到中间数组B中 for (int i : A) { pxA[i - min] += 1;//数的位置 上+1 } //四.遍历输出 //建立最终数组,就是返回的数组,和原始数组长度相等,可是排序完成的 int[] result = new int[A.length]; int index = 0;//记录最终数组的下标 //先循环每个元素 在计数排序器的下标中 for (int i = 0; i < pxA.length; i++) { //循环出现的次数 for (int j = 0; j < pxA[i]; j++) {//pxA[i]:这个数出现的频率 result[index++] = i + min;//原来原来减小了min如今加上min,值就变成了原来的值 } } return result; }
也是典型的用空间换时间的算法
时间复杂度: O(n+k)
空间复杂度: O(n+k)
稳定性: 稳定