快速排序
原理
从序列中选择一个轴点元素(pivot)
假设每次选择 0 位置的元素为轴点元素
利用 pivot 将序列分割成 2 个子序列
1. 将小于 pivot 的元素放在pivot前面(左侧)
2. 将大于 pivot 的元素放在pivot后面(右侧)
3. 等于pivot的元素放哪边都可以
对子序列进行上述操作, 直到不能再分割(子序列中只剩下1个元素)
# 极限情况 : 轴点构造后, 轴点两边极度不均衡, 例 : [0] [pivot] [length - 1]
轴点构造
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T83TcknJ-1604243291801)(…/img/快速排序.PNG)]
代码
/**
* 门面方法
*/
public static Integer[] quickSort(Integer[] array) {
return quickSort(array, 0, array.length);
}
/**
* 核心方法
*/
public static Integer[] quickSort(Integer[] array, int begin, int end) {
// 一个元素直接返回
if (end - begin < 2) {
return array;
}
Integer[] sortArray = array;
// 轴点构造, 并获取下次轴点构造的参数, mid 为轴点的现在位置下标
int mid = pivotIndex(array, begin, end);
// 左右部分快速排序
quickSort(array, begin, mid);
quickSort(array, mid + 1, end);
return array;
}
/**
* 对 array[begin, end) 进行轴点构造
*/
private static int pivotIndex(Integer[] array, int begin, int end) {
// 防止出现极限情况
Util.swap(begin,begin + (int) (Math.random() * (end - begin)),array);
Integer value = array[begin];
// 获取末尾元素下标
end--;
// 开始轴点构造, begin 和 end 重叠结束
while (begin < end) {
// array[end] 的交换
while (begin < end) {
// array[end] 值小, 需要交换, 防止极限情况
if (array[end] <= value) {
array[begin] = array[end];
break;
} else {
end--;
}
}
// array[begin] 的交换
while (begin < end) {
// array[begin] 值大, 需要交换, 防止极限情况
if (array[begin] > value) {
array[end] = array[begin];
break;
} else {
begin++;
}
}
}
// value 重新进入数组
array[begin] = value;
return begin;
}
希尔排序
原理
# 希尔排序 / 递减增量排序 (Diminishing Increment Sort)
把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序, m 从某个整数逐渐减为 1 , 当 𝑚 为 1 时,整个序列将完全有序
# 底层排序
不难看出来, 逆序对的数量在逐渐减少
因此希尔排序底层一般使用插入排序对每一列进行排序,也很多资料认为希尔排序是插入排序的改进版
代码
/**
* 门面
*/
public static Integer[] shellSort(Integer[] array) {
Integer[] sortArray = array;
ArrayList<Integer> steps = step(sortArray);
// 循环步数
for (Integer step : steps) {
shellSort(sortArray, step);
}
return sortArray;
}
/**
* 核心方法, 底层是插入排序, 但是修改插入步数
*/
private static Integer[] shellSort(Integer[] array, int step) {
for (int i = step; i < array.length; i++) {
int flag = i;
// 获取要查入对象的值
int value = array[flag];
// 迭代, 凡是不符合要求的, 往后移动一位
while (flag >= step && value < array[flag - step]) {
array[flag] = array[flag - step];
flag = flag - step;
}
// 正式插入
array[flag] = value;
}
return array;
}
/**
* 获取步数集合 steps : {length / 2, steps[i - 1] / 2, ......}, 上一个元素除二
*/
private static ArrayList<Integer> step(Integer[] array) {
ArrayList<Integer> steps = new ArrayList<>();
int step = array.length;
while ((step = (step / 2)) >= 1) {
steps.add(step);
}
return steps;
}
计数排序
原理
# 计数排序、桶排序、基数排序,都不是基于比较的排序
它们是典型的用空间换时间,在某些时候,平均时间复杂度可以比 O(nlogn) 更低
# 原理 :
统计每个整数在序列中出现的次数,进而推导出每个整数在有序序列中的索引
代码
// 获取数组最大值最小值
private static Integer[] getMaxAndMin(Integer[] array) {
Integer[] maxAndMin = {array[0], array[0]};
for (int i = 1; i < array.length; i++) {
if (array[i] > maxAndMin[0]) {
maxAndMin[0] = array[i];
}
if (array[i] < maxAndMin[1]) {
maxAndMin[1] = array[i];
}
}
return maxAndMin;
}
/**
* 计算 count 数组
*/
private static int[] count(Integer[] array) {
Integer[] maxAndMin = getMaxAndMin(array);
int[] count = new int[maxAndMin[0] - maxAndMin[1] + 1];
// count 数组存放将要排序数组的各个元素个数
for (int i = 0; i < array.length; i++) {
count[array[i] - maxAndMin[1]]++;
}
// count 数组存放未来排好序数组的下标
for (int i = 1; i < count.length; i++) {
count[i] = count[i - 1] + count[i];
}
return count;
}
/**
* 核心
*/
public static Integer[] countSort(Integer[] array) {
Integer[] sortArray = new Integer[array.length];
int[] count = count(array);
Integer[] maxAndMin = getMaxAndMin(array);
// 按照 count 数组将 array 数组放入 sortArray 数组
for (int i = array.length - 1; i >= 0; i--) {
sortArray[--count[array[i] - maxAndMin[1]]] = array[i];
}
return sortArray;
}
基数排序
原理
基数排序非常适合用于整数排序 (尤其是非负整数)
# 执行流程:
依次对个位数、十位数、百位数、千位数、万位数...进行排序 (从低位到高位)
代码
public static Integer[] radixSort(Integer[] array) {
Integer[] maxAndMin = getMaxAndMin(array);
// 在最大值位数范围内进行计数排序
for (int i = 1; i <= maxAndMin[0]; i = i * 10) {
array = radixSort(array, i);
}
return array;
}
private static Integer[] radixSort(Integer[] array, int divider) {
// 已知一定在 [0,9] 范围内
Integer[] sortArray = new Integer[array.length];
int[] count = new int[10];
// 计数排序
for (int i = 0; i < array.length; i++) {
count[array[i] / divider % 10]++;
}
for (int i = 1; i < count.length; i++) {
count[i] = count[i - 1] + count[i];
}
for (int i = array.length - 1; i >= 0; i--) {
sortArray[--count[array[i] / divider % 10]] = array[i];
}
return sortArray;
}
桶排序
原理
# 执行流程
1. 创建一定数量的桶(比如用数组、链表作为桶)
2. 按照一定的规则(不同类型的数据,规则不同),将序列中的元素均匀分配到对应的桶
3. 分别对每个桶进行单独排序
4. 将所有非空桶的元素合并成有序序列
# 没有答案, 按照不同情况会有不同结果
代码
public static Integer[] bucketSort(Integer[] array) {
Integer[] maxAndMin = getMaxAndMin(array);
Integer[] sortArray = new Integer[array.length];
List<Integer>[] buckets = new List[maxAndMin[0] + 1];
// 将数组元素放入桶
for (int i = 0; i < array.length; i++) {
List<Integer> bucket = buckets[array[i]];
if (bucket == null) {
bucket = new LinkedList<Integer>();
buckets[array[i]] = bucket;
}
bucket.add(array[i]);
}
// 桶排序, 并且将桶中元素放入数组中
int flag = 0;
for (int i = 0; i < buckets.length; i++) {
if (buckets[i] == null) continue;
// 桶内排序
Integer[] sort = buckets[i].toArray(new Integer[buckets[i].size()]);
sort = insertSort(sort);
// 放入
for (int j = 0; j < sort.length; j++) {
sortArray[flag++] = sort[j];
}
}
return sortArray;
}
nue;
// 桶内排序
Integer[] sort = buckets[i].toArray(new Integer[buckets[i].size()]);
sort = insertSort(sort);
// 放入
for (int j = 0; j < sort.length; j++) {
sortArray[flag++] = sort[j];
}
}
return sortArray;
}