排序算法分类
1:除了函数调用所需的栈和固定数目的实例变量之外无需额外内存的原地排序算法
2:需要额外内存空间来存储了一份数组副本的其他排序方法
选择排序:
/** * 选择排序 * <p> * 得到最小值数组下标minIndex,和当前下标i相交换 * 1.运行时间和输入无关; * 2.数据移动最少:交换次数和数组的大小是线性关系 * 对于长度为N的数组,选择排序大约需要约N^2/2次比较和N次交换 */ public static void selectSort(Comparable[] array) { int length = array.length; for (int i = 0; i < length; i++) { int minIndex = i; for (int j = i + 1; j < length; j++) { if (less(array[j], array[minIndex])) { minIndex = j; } } // i从0到length-1,j从i+1到length-1,找出[i+1,length-1]中的最小值 exchange(array, i, minIndex); } }
插入排序:
/** * 插入排序 * <p> * 下标从1开始;当前值左侧都是有序的,当前值与左侧各值相比较,交换 * 长度为N的数组,平均情况下插入排序需要约N^2/4次比较以及约N^2/4次交换 * 最坏情况下需要N^2/2次比较和N^2/2次交换, * 最好情况下需要N-1次比较和0次交换 */ public static void insertSort(Comparable[] array) { int length = array.length; for (int i = 1; i < length; i++) { for (int j = i; j > 0; j--) { // 前一个值大于后一个值,(左侧两两比较) if (less(array[j], array[j - 1])) { exchange(array, j, j - 1); } } } }
总结:
对于随机排序且无重复主键的数组,插入排序和选择排序的运行时间是平方级别的
两者之比应该是一个较小的常数。
插入排序一般比选择排序快点。
拓展:
倒置指的是数组中的两个顺序颠倒的元素,
如果数组中倒置的数量小于数组大小的某个倍数,那么这个数组就是部分有序
典型的部分有序数组:
1.数组中每个元素距离它最终的位置都不远
2.一个有序大数组接一个小数组
3.数组中只有几个元素的位置不确定
当倒置的数量很少时,插入排序可能是排序中最快的
插入排序需要的交换操作和数组中倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组大小再减一
插入排序对于部分有序的数组十分高效,也适合小规模数组
快速排序
/** * 快速排序:时间复杂度nlogn * <p> * 应用广泛,因为实现简单,适用于各种不同的输入数据且算法速度快 * <p> * 快排是一种分治的排序算法:它将数组分成两个子数组,两部分独立排序 * 思想:两个数组都有序的时候,整个数组就有序了 * 切分的位置取决于数组的内容 * 比如输入: K R A T E L E P U I M Q C X O S 目标:K * 切分输出: (E C A I E) < K > (L P U T M Q R X O S) * 然后K的左右边都进行排序,排序后整个数组就是有序的了 */ public static void quickSort(Comparable[] array, int lower, int higher) { if (lower < 0 || higher <= 0 || lower >= higher) { return; } int j = partition(array, lower, higher); // 左边排序 quickSort(array, lower, j - 1); // 右边排序 quickSort(array, j + 1, higher); } // 切分 private static int partition(Comparable[] array, int lower, int higher) { int i = lower; int j = higher + 1; Comparable v = array[lower]; while (true) { // 找到比v小的值或i==higher,取下标i while (less(array[++i], v)) { if (i == higher) { break; } } // 找到比v大的值或j==lower,取下标j while (less(v, array[--j])) { if (j == lower) { break; } } // 最后相邻元素比较 // 6 1 2 5 (4) 3 (9) 7 10 8 if (i >= j) { break; } // 找到比v大的下标i和比v小的下标j,交换 exchange(array, i, j); } exchange(array, lower, j); return j; }
冒泡排序
/** * 优化后的冒泡排序:时间复杂度 n*n * <p> * 如果在一趟排序中,数组元素没有发生过交换说明数组已经有序,跳出循环即可 */ public static void bubbleSort2(Comparable[] array) { int compareRange = array.length - 1; boolean flag = true; while (flag) { flag = false; for (int j = 0; j < compareRange; j++) { if (less(array[j + 1], array[j])) { exchange(array, j, j + 1); flag = true; } } compareRange -= 1; } } /** * 冒泡排序:时间复杂度 n*n * <p> * 比较是相邻的两个元素比较,交换也发生在这两个元素之间 * 元素相等则不会发生交换,所以冒泡排序是稳定的 */ public static void bubbleSort1(Comparable[] array) { // 一共N-1趟 for (int i = 0; i < array.length - 1; i++) { // 第i+1趟时,需要比较的次数(排好序的元素设置一个边界) for (int j = 0; j < array.length - 1 - i; j++) { if (less(array[j + 1], array[j])) { exchange(array, j, j + 1); } } } }
附:
/* 需要实现Comparable接口 */ @SuppressWarnings("unchecked") private static boolean less(Comparable var1, Comparable var2) { return var1.compareTo(var2) < 0; } private static void exchange(Comparable[] target, int i, int j) { Comparable temp = target[i]; target[i] = target[j]; target[j] = temp; }