TopK问题
排序
1、排序的稳定性
2、插入排序
/**
* 插入排序
*
* 时间复杂度:
* 最好情况:O(n) 当 数组已经有序
* 最坏情况:O(n^2) 当 数据是逆序
* 平均情况:O(n^2)
* 对于插入排序,数组越趋于有序,耗时越少
*
* 空间复杂度:
* 最好/平均/最坏:O(1)
*
* 稳定性:稳定
* @param array
*/
public static void insert2(long[] array) {
for (int i = 1; i < array.length; i++) { // 循环 n - 1 次
// 有序区间: [0, i)
// 无序区间: [i, array.length)
long x = array[i];
int j;
for (j = i - 1; j >= 0 && array[j] > x; j--) {
array[j + 1] = array[j];
}
array[j + 1] = x;
}
}
public static void insertSort(long[] array) {
// 要对 array.length 个元素做排序,需要多少次 查找+插入 的过程?
// array.length - 1
// 外层的循环:需要进行很多次的 查找+插入
for (int i = 0; i < array.length - 1; i++) {
// 有序区间
// 第一次(i == 0): [0, 0] / [0, 1)
// 第二次(i == 1): [0, 1] / [0, 2)
// ...
// 第 i - 1 次(i): [0, i] / [0, i + 1)
// 有序区间:[0, i]
// 无序区间:[i + 1, array.length)
// 要插入的元素的下标是无序区间的第一个元素(紧挨着有序区间的下一个元素)
long x = array[i + 1];
// 查找并插入,遍历整个有序区间: [0, i]
int j;
for (j = i; j >= 0 && array[j] > x; j--) {
array[j + 1] = array[j];
}
array[j + 1] = x;
}
}
public static void 查找并插入整理版本(long[] array, int index) {
// 有序区间: [0, index)
long x = array[index];
int i;
for (i = index - 1; i >= 0 && array[i] > x; i--) {
array[i + 1] = array[i];
}
array[i + 1] = x;
}
3、冒泡排序
/**
* 冒泡排序:
*
* 时间复杂度:
* 最好情况:O(n) 当 数组已经有序
* 平均情况:O(n^2)
* 最坏情况:O(n^2) 当 数组是逆序的
*
* 空间复杂度:
* 最好/平均/最坏:O(1)
*
* 稳定性:具备稳定性
* @param array
*/
public static void bubbleSort(long[] array) {
for (int i = 0; i < array.length - 1; i++) { // 需要 n - 1 次冒泡过程
// 无序区间 [0, array.length - i)
// 遍历整个无序区间,但是确保相邻的元素两两比较,所以
// 要遍历的下标范围 [0, array.length - i - 1)
// 每次冒泡过程时,假设无序区间是有序的
boolean sorted = true;
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
// 这里不加等号,加等号就失去稳定性
swap(array, j, j + 1);
sorted = false; // 存在 前面的元素 > 后边的元素,说明假设无序区间有序的假设不成立
// 冒泡的过程中,一旦发生了交换,说明
// 存在:相邻的两个元素之间,前面的元素 > 后边的元素
// 反过来说:单次冒泡给过程,如果一次交换都没有发生过
// 说明不存在:相邻的两个元素之间,前面的元素 > 后边的元素
// 任意相邻的两个元素之间,前面的元素 <= 后边的元素
// 冒泡过程只在无序区间进行
// 无序区间的任意相邻的两个元素满足:前面的元素 <= 后边的元素
// 我们认为的无序区间是有序的
// 所以,整个数组已经全部是有序的了
}
}
// 冒泡过程完成之后,如果 sorted == true,说明一次交换没发生过
if (sorted) {
return;
}
}
}
4、选择排序
public static void selectSort(long[] array) {
for (int i = 0; i < array.length - 1; i++) { // 一共进行 n - 1 次选择过程
// [无序区间] [有序区间]
// 无序区间: [0, array.length - i)
// 有序区间: [array.length - i, array.length)
// 1. 遍历整个无序区间,找到最大的元素所在的下标(不需要知道最大的元素是多少,而是它在哪)
int maxIdx = 0;
// 在剩余位置,继续找比 array[maxIdx] 元素还大的元素
for (int j = 1; j < array.length - i; j++) {
// 期间,如果出现更的元素,则更新最大元素所在下标
if (array[j] > array[maxIdx]) {
maxIdx = j;
}
}
// 最大的元素在 [maxIdx],即 array[maxIdx] 是无序区间的最大元素
// 把最大的元素放到无序区间的最后一个位置
// [0, array.length - i)
// 无序区间的最后一个位置下标是 [array.length - i - 1]
swap(array, maxIdx, array.length - i - 1);
}
}
/**
* 选择排序:
*
* 时间复杂度:
* 最好/平均/最坏:O(n^2)
*
* 空间复杂度:
* 最好/平均/最坏:O(1)
*
* 稳定性:保证不了稳定性
* @param array
*/
public static void selectSort2(long[] array) {
for (int i = 0; i < array.length - 1; i++) {
// 有序区间: [0, i)
// 无序区间: [i, array.length)
int minIdx = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIdx]) {
minIdx = j;
}
}
// 交换 [minIdx] 和 [i] 位置的元素
swap(array, minIdx, i);
}
}
5、希尔排序
/**
* 希尔排序
*
* 时间复杂度: O(n ^ 1.25)
*
* 空间复杂度: O(1)
*
* 稳定性: 不稳定
* @param array
*/
public static void shellSort(long[] array) {
if (array.length == 0) {
return;
}
int gap = array.length / 2;
while (true) {
// 带有间隔的分组插排
for (int i = gap; i < array.length; i++) {
long x = array[i];
int j;
for (j = i - gap; j >= 0 && array[j] > x; j = j - gap) {
array[j + gap] = array[j];
}
array[j + gap] = x;
}
if (gap == 1) {
// 说明上一次的分组插排其实最终的插排
break;
}
gap = gap / 2;
}
}
6、堆排序
public static void adjustDown大堆(long[] array, int size, int index) {
while (2 * index + 1 < size) {
int maxIdx = 2 * index + 1;
if (maxIdx + 1 < size && array[maxIdx + 1] > array[maxIdx]) {
maxIdx++;
}
if (array[index] >= array[maxIdx]) {
return;
}
swap(array, index, maxIdx);
index = maxIdx;
}
}
public static void createHeap大堆(long[] array, int size) {
for (int i = (size - 2) / 2; i >= 0; i--) {
adjustDown大堆(array, size, i);
}
}
/**
* 堆排序:
*
* 时间复杂度:
* 最好/平均/最坏:O(n * log(n))
*
* 空间复杂度:O(1)
*
* 稳定性:不稳定(向下调整的过程中,相对位置可能被破坏)
* @param array
*/
public static void heapSort(long[] array) {
// 将整个数组(整个数组都是无序区间)创建成大堆
createHeap大堆(array, array.length); // O(n) or O(n * log(n))
// 循环选择的过程,一共需要 n - 1 次 O(n * log(n))
for (int i = 0; i < array.length - 1; i++) { // 循环 n 次
// 无序区间: [0, array.length - i)
// 交换最大的元素(下标是 [0])和无序区间的最后一个元素(下标是 array.length - i - 1)
swap(array, 0, array.length - i - 1); // O(1)
// 由于最大的元素已经放到最后了,所以,无序区间:[0, array.length - i - 1)
// 无序区间的元素个数: array.length - i - 1 - 0 = array.length - i - 1
// 为了维护无序区间的大堆性质,对无序区间剩余的元素,在 [0] 做向下调整
adjustDown大堆(array, array.length - i - 1, 0); // O(log(n))
}
}
public static void main(String[] args) {
long[] array = {3, 9, 1, 4, 5, 8, 2, 6, 0, 7};
heapSort(array);
System.out.println(Arrays.toString(array));
}
}
7、快速排序
8、hoare法
9、挖坑法
10、前后指针
public static void 进阶版Partition(long[] array, int from, int to) {
long pivot = array[to];
/*
[from, b) 小于基准值
[b, d) 等于基准值
[d, g] 未比较的元素
(g, to] 大于基准值
*/
int b = from;
int d = from;
int g = to;
// [d, g] 这个区间内没有元素时停止; d <= g 说明还有元素
while (d <= g) {
if (array[d] == pivot) {
d++;
} else if (array[d] < pivot) {
swap(array, d, b);
b++;
d++;
} else {
// array[d] > pivot
swap(array, d, g);
g--;
}
}
}
11、归并排序
/**
* 归并排序
*
* 时间复杂度:
* 最好/平均/最坏: O(n * log(n))
*
* 空间复杂度:
* 最好/平均/最坏: O(n)
*
* 稳定型:具备稳定性
* @param array
*/
public static void mergeSort(long[] array) {
mergeSortInternal(array, 0, array.length);
}
// 待排序区间:[from, to)
private static void mergeSortInternal(long[] array, int from, int to) {
if (to <= 1 + from) {
return;
}
// 把待排序区间对半分开
// 找到中间位置下标
int mid = from + (to - from) / 2;
// 整个区间被切割成
// [from, mid)
// [mid, to)
// 按照相同的方式,先把左右两个区间变得有序
mergeSortInternal(array, from, mid);
mergeSortInternal(array, mid, to);
// [from, mid) 有序了
// [mid, to) 有序了
merge(array, from, mid, to);
}
private static void merge(long[] array, int from, int mid, int to) {
int size = to - from;
// 需要的额外空间
long[] e = new long[size];
int eIdx = 0;
int leftIdx = from; // 左边区间要处理的元素的下标
int rightIdx = mid; // 右边区间要处理的元素的下标
// 什么条件表示左边区间 [from, mid) 就没有元素了 leftIdx == mid
// 同理右边区间:rightIdx == to
// 两个区间都有元素的时候才要比较
// 左区间有元素:leftIdx < mid
// 右: rightIdx < to
// leftIdx < mid && rightIdx < to
while (leftIdx < mid && rightIdx < to) {
// 要比较的两个元素 array[leftIdx] 和 array[rightIdx]
if (array[leftIdx] <= array[rightIdx]) {
// 取左边的元素放入 e 数组
e[eIdx] = array[leftIdx];
eIdx++;
leftIdx++;
} else {
// 取右边的元素放入 e 数组
e[eIdx] = array[rightIdx];
eIdx++;
rightIdx++;
}
}
// 说明有一个区间的元素被全部取完了
if (leftIdx < mid) {
// 右边取完了,把左边的所有元素依次放到 e 中
while (leftIdx < mid) {
e[eIdx] = array[leftIdx];
eIdx++;
leftIdx++;
}
} else {
while (rightIdx < to) {
e[eIdx] = array[rightIdx];
eIdx++;
rightIdx++;
}
}
// e 里是有序的 [0, size)
// 要把有序的元素从 e 中搬回到 array 中 [from, to)
for (int i = 0; i < size; i++) {
array[from + i] = e[i];
}
}
12、总结