代码中的less是比较两个数的大小,swap是对两个数进行交换。
冒泡排序
冒泡排序是最简单的排序算法,冒泡排序从前往后依次比较相邻两个元素的大小,前面一个比后面一个大就进行交换,这样一轮下来,最后一个元素就是最大值。然后对前面n-1个数重复这个过程,最后完成排序。
public class BubbleSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for (int i = 1; i < arr.length; i++) {
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (less(arr[j], arr[j + 1])) {
swap(arr, j ,j+1);
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
}
选择排序
选择排序是每次选择最大(或最小)的值放到最后。
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 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);
}
}
}
}
希尔排序
希尔排序是在插入排序基础上的改进,插入排序每次只能使得逆序数量减少1,希尔排序通过交换不相邻元素,使得每次逆序的改变可以大于1。希尔排序通过设置一个间隔长度h,每次都对相邻h个单位的元素序列进行插入排序,然后逐渐减小h,知道h为1时完成排序。
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;
}
}
}
最外层while循环逐渐减小h,第一个for循环是循环分组的数量,最内层循环是每组内的排序次数。
归并排序
归并是指将两个已经排好序的数组每一个元素分别进行比较然后放入另一个数组得到一个有序数组。
归并需要一个额外的数组空间,归并方法如下:
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++];
}
}
}
}
归并排序建立在归并方法的基础上,分为自顶向下和自底向上两种方法, 归并排序的时间复杂度为O(NlogN):
- 自底向上的方法
自底向上的方法先归并小数组,然后再对归并好的小数组做归并,逐渐得到有序数组。
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));
}
}
}
}
- 自顶向下
自顶向下的方法每次将问题分解为两个数组的归并,一般采用递归来实现。
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);
}
}
快速排序
快速排序先找寻一个基准,然后将小于基准的防到一边,大于基准的防到另一边。然后递归的对两个子序列继续进行划分。快速排序的时间复杂度也是O(NlogN),但是在最坏的情况下,也就是序列已经排好序的情况下,时间复杂度会达到O(n),但是平均情况下快速排序的效率比归并更高。
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
@Override
public void sort(T[] nums) {
shuffle(nums);
sort(nums, 0, nums.length - 1);
}
private void sort(T[] nums, int l, int h) {
if (h <= l)
return;
int j = partition(nums, l, h);
sort(nums, l, j - 1);
sort(nums, j + 1, h);
}
private void shuffle(T[] nums) {
List<Comparable> list = Arrays.asList(nums);
Collections.shuffle(list);
list.toArray(nums);
}
}
其中最重要的是partition切分函数,这个函数是用于将大于基准的数与小于基准的数进行划分的方法。使用双指针的方法,左右指针都向中间移动,左指针移动到第一个大于基准值的时候停止,右指针移动到第一个小于基准的时候停止,然后交换两个指针指向的元素。重复这个过程,直到左指针大于等于右指针,然后将基准与右指针所在位置元素交换。
private int partition(T[] nums, int l, int h) {
int i = l, j = h + 1;
T v = nums[l];
while (true) {
while (less(nums[++i], v) && i != h) ;
while (less(v, nums[--j]) && j != l) ;
if (i >= j)
break;
swap(nums, i, j);
}
swap(nums, l, j);
return j;
}