快速排序
快速排序(Quicksort) 是一种排序算法,平均时间复杂度为:O(n log n),最坏需要 O(n²),但很少见,快速排序之所以叫快速排序,就是因为它比一般的排序算法要快。
快速排序使用了分而治之的思想,步骤如下:
- 选择基准值(pivot)
- 将数组分成两个子数组:小于基准值的元素和大于基准值的元素
- 对这两个子数组进行快速排序
不同的选取基数值策略对排序性能都有很大的影响。
递归实现:
public static void quickSort(int[] arr) {
qsort(arr, 0, arr.length - 1);
}
private static void qsort(int[] arr, int low, int high) {
System.out.println(String.format("qsort() -> low:%s, high:%s", low, high));
if (low < high) {
//将数组分为两部分
int pivot = partition(arr, low, high);
//递归排序左子数组
qsort(arr, low, pivot - 1);
//递归排序右子数组
qsort(arr, pivot + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
//枢轴记录
int pivot = arr[low];
while (low < high) {
while (low < high && arr[high] >= pivot) --high;
//交换比枢轴小的记录到左端
arr[low] = arr[high];
while (low < high && arr[low] <= pivot) ++low;
//交换比枢轴小的记录到右端
arr[high] = arr[low];
}
//扫描完成,枢轴到位
arr[low] = pivot;
//返回的是枢轴的位置
return low;
}
非递归实现版:
public static void quickSort(int[] array){
if (array == null || array.length == 1) return;
//存放开始与结束索引
Stack s = new Stack();
//压栈
s.push(0);
s.push(array.length - 1);
//利用循环里实现
while (!s.empty()) {
int right = s.pop();
int left = s.pop();
//如果最大索引小于等于左边索引,说明结束了
if (right <= left) continue;
int i = partition(array, left, right);
if (left < i - 1) {
s.push(left);
s.push(i - 1);
}
if (i + 1 < right) {
s.push(i+1);
s.push(right);
}
}
}
归并排序
归并排序(mergesort)时间复杂度:O(n log n),同样采用分治法实现。
步骤:
- 分割:递归地把当前序列平均分割成两半
- 归并:在保持元素顺序的同时将上一步得到的子序列集成到一起
递归实现:
public static void sort(int[] data) {
doSort(data, 0, data.length - 1);
}
public static void doSort(int[] data, int left, int right) {
if (left >= right)
return;
System.out.println();
// 找出中间索引
int center = (left + right) / 2;
// 对左边数组进行递归
doSort(data, left, center);
// 对右边数组进行递归
doSort(data, center + 1, right);
// 合并
merge(data, left, center, right);
}
/**
* 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
*
* @param data
* 数组对象
* @param left
* 左数组的第一个元素的索引
* @param center
* 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
* @param right
* 右数组最后一个元素的索引
*/
private static void merge(int[] data, int left, int center, int right) {
System.out.println(String.format("->\nleft:%s,center:%s,right:%s\nbefore:", left, center, right));
Util.printArray(data);
// 临时数组
int[] tmpArr = new int[data.length];
// 右数组第一个元素索引
int mid = center + 1;
// third 记录临时数组的索引
int third = left;
// 缓存左数组第一个元素的索引
int tmp = left;
while (left <= center && mid <= right) {
// 从两个数组中取出最小的放入临时数组
if (data[left] <= data[mid]) {
tmpArr[third++] = data[left++];
} else {
tmpArr[third++] = data[mid++];
}
}
// 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
while (mid <= right) {
tmpArr[third++] = data[mid++];
}
while (left <= center) {
tmpArr[third++] = data[left++];
}
// 将临时数组中的内容拷贝回原数组中
// (原left-right范围的内容被复制回原数组)
while (tmp <= right) {
data[tmp] = tmpArr[tmp++];
}
System.out.println("\nafter:");
Util.printArray(data);
System.out.println("\n<-\n\n");
}
非递归实现:
public static void sort(int[] arr) {
int[] orderedArr = new int[arr.length];
for (int i = 2; i < arr.length * 2; i *= 2) {
for (int j = 0; j < (arr.length + i - 1) / i; j++) {
int left = i * j;
int mid = left + i / 2 >= arr.length ? (arr.length - 1) : (left + i / 2);
int right = i * (j + 1) - 1 >= arr.length ? (arr.length - 1) : (i * (j + 1) - 1);
int start = left, l = left, m = mid;
while (l < mid && m <= right) {
if (arr[l] < arr[m]) {
orderedArr[start++] = arr[l++];
} else {
orderedArr[start++] = arr[m++];
}
}
while (l < mid)
orderedArr[start++] = arr[l++];
while (m <= right)
orderedArr[start++] = arr[m++];
System.arraycopy(orderedArr, left, arr, left, right - left + 1);
}
}
}
关于它们具体的工作原理网上多的是,但真的把代码整整齐齐的写出来的还真不多,白看不如一敲,我还是直接放代码比较好。具体的详细原理介绍可以随便搜一下有助于理解,我这里就不 copy 啦。
最近在学算法,后面我还会继续把常见的几种排序法发出来。