初级排序算法:
选择排序 从数组中选择最小元素,将它与数组的第一个元素交换位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
特点:时间与输入数组的性质无关(已经很慢了),就算排好了也全走一遍
数据移动少,每次移动俩。
public class Selection<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int N = nums.length; for (int i = 0; i < N - 1; i++) { int min = i; for (int j = i + 1; j < N; j++) { if (less(nums[j], nums[min])) { min = j; } } swap(nums, i, min); } } }
冒泡排序 从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。在一轮循环中,如果没有发生交换,那么说明数组已经是有序的,此时可以直接退出。
public class Bubble<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int N = nums.length; boolean isSorted = false; for (int i = N - 1; i > 0 && !isSorted; i--) { isSorted = true; for (int j = 0; j < i; j++) { if (less(nums[j + 1], nums[j])) { isSorted = false; swap(nums, j, j + 1); } } } } }
插入排序 从a[0]开始,与它前面的元素做比较,满足小于等于则做交换(将当前元素插入到左侧已经排序的数组),使得插入之后左侧数组依然有序。
特点:对部分有序的数组性能优越
适合小规模数组
public class Insertion<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int N = nums.length; for (int i = 1; i < N; i++) { for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) { swap(nums, j, j - 1); } } } }
希尔排序 将h间隔内的数进行插入排序(跨着排),逐步减小h。其中h初始化为1,4,13,40(3*h+1)且满足N/3的最大值。每次h/3
特点:数组越大,优势越明显。
代码量小
无需额外空间
实际上是优化了的插入排序,减小低效交换次数加快速度
public class Shell<T extends Comparable<T>> extends Sort<T> { @Override public void sort(T[] nums) { int N = nums.length; int h = 1; while (h < N / 3) { h = 3 * h + 1; // 1, 4, 13, 40, ... } while (h >= 1) { for (int i = h; i < N; i++) { for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h) { swap(nums, j, j - h); } } h = h / 3; } } }
归并排序 分为自顶向下与自底向上使用归并方法
public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> { protected T[] aux; protected void merge(T[] nums, int l, int m, int h) { int i = l, j = m + 1; for (int k = l; k <= h; k++) { aux[k] = nums[k]; // 将数据复制到辅助数组 } for (int k = l; k <= h; k++) { if (i > m) { nums[k] = aux[j++]; } else if (j > h) { nums[k] = aux[i++]; } else if (aux[i].compareTo(aux[j]) <= 0) { nums[k] = aux[i++]; // 先进行这一步,保证稳定性 } else { nums[k] = aux[j++]; } } } }
自顶向下:递归分成小段排序,归并
public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> { @Override public void sort(T[] nums) { aux = (T[]) new Comparable[nums.length]; sort(nums, 0, nums.length - 1); } private void sort(T[] nums, int l, int h) { if (h <= l) { return; } int mid = l + (h - l) / 2; sort(nums, l, mid); sort(nums, mid + 1, h); merge(nums, l, mid, h); } }
自底向上:先遍历,两两归并(最小的归并就是排序),子数组大小每次加倍。(不需要递归,代码量较少)
public class Down2UpMergeSort<T extends Comparable<T>> extends MergeSort<T> { @Override public void sort(T[] nums) { int N = nums.length; aux = (T[]) new Comparable[N]; for (int sz = 1; sz < N; sz += sz) { for (int lo = 0; lo < N - sz; lo += sz + sz) { merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1)); } } } }
特点:需要的额外空间与待排序数组大小N成正比(空间复杂度不佳)
最差情形与一般情形时间复杂度相同:都为NlgN,在基于比较排序的算法中渐进最优。
堆排序 应用优先队列对元素排序,建堆并且不断调整堆,不断取下最大值调整(递归)
特点:同时最好地利用时间空间的排序算法
代码简洁
无法使用缓存
快速排序 将待排的数组分为两个子数组,其中左子数组全小于中间元素,右子数组全大于中间元素。两个子数组分别排序
特点:内循环简洁,不在内循环中移动数据,速度快
比较次数较少,代码量少
问题较多,较为脆弱
偏爱随机性,排前打乱数组
改进版快排----三向切分(适用于待排序的数组中存在大量重复数据,开销由线性对数降低到线性),三个指针分三部分。