堆排序
性能:时间复杂度 O(nlogn) ,空间复杂度 O(1),不稳定
思路:把数组看做一个完全二叉树的结构,建立大顶堆(或小顶堆),排序就是把最顶层的根节点与末尾元素交换,然后继续从最顶层的根节点开始维护堆,循环往复就变成一个有序集合了。
-
大顶堆 :每个节点的值都大于或等于其子节点的值。堆顶(根节点)是整个堆的最大值。
-
小顶堆 :每个节点的值都小于或等于其子节点的值。堆顶(根节点)是整个堆的最小值。
最开始建堆时,从最后一个非叶子节点开始(因为在对一个节点进行操作时,其实是需要其子节点已经是大顶堆(或小顶堆),其实从我们要做的事情也可以理解,因为我们是要把最大或最小移动到最顶层,所以也应该是自底向上),自底向上调整堆。
代码如下:
java
public class HeapSort {
/**
* 堆排序
* @param arr 待排序数组
*/
public static void sort(int[] arr) {
int n = arr.length;
// 从最后一个非叶子结点开始调整堆
for (int i = n / 2 - 1; i >= 0; i--) {
adjustHeapIterative(arr, i, n);
}
// 排序,把最大值依次放入末尾,继续调整接下来的堆
for (int i = n - 1; i > 0; i--) {
swap(arr, 0, i);
adjustHeapIterative(arr, 0, i);
}
}
/**
* 迭代方式的堆调整(下沉)操作 O(1)
* @param arr 待调整的数组
* @param root 要下沉的根节点索引
* @param len 堆的有效大小
*/
private static void adjustHeapIterative(int[] arr, int root, int len) {
int temp = arr[root]; // 保存根节点的值
int current = root; // 当前要调整的节点
// 从根节点开始向下调整
while (current * 2 + 1 < len) { // 如果有左孩子
int left = current * 2 + 1;
int right = current * 2 + 2;
int largest = left; // 假设左孩子是较大的
// 如果右孩子存在且比左孩子大
if (right < len && arr[right] > arr[left]) {
largest = right;
}
// 如果较大的孩子比当前节点大,需要交换
if (arr[largest] > temp) {
arr[current] = arr[largest]; // 将较大的孩子上移
current = largest; // 继续向下调整
} else {
break; // 当前节点已经比两个孩子都大,调整结束
}
}
arr[current] = temp; // 将最初根节点的值放到最终位置
}
/**
* 调整堆(递归)使用递归还是会消耗栈空间
* @param arr 数组
* @param root 调整的根节点
* @param len 边界
*/
private static void adjustHeap(int[] arr, int root, int len) {
int tempRoot = root;
int left = tempRoot * 2 + 1;
int right = tempRoot * 2 + 2;
// 找到最大的子节点交换
if (left < len && arr[left] > arr[tempRoot]) {
tempRoot = left;
}
if (right < len && arr[right] > arr[tempRoot]) {
tempRoot = right;
}
// 调整有变动的子树
if (tempRoot != root) {
swap(arr, tempRoot, root);
adjustHeap(arr, tempRoot, len);
}
}
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
快速排序
性能:时间复杂度O(nlogn)~O(n^2) ,空间复杂度 O(log n) ~ O(n),不稳定
思路:对于一个待排序数组,选取一个基准值,把比它小的元素移动到左边,其他移动到右边,也就是左边的元素都比右边的元素小,然后再递归处理左边和右边的元素,最终有序。
基准值的选取:通常是采用随机的方式,因为理想状态是把数组对半分,这样效率是最好的,避免这个基准值过于边缘,这样可能出现最差时间复杂度 O(n^2),通过随机来进行平衡。
代码如下:
java
import java.util.Random;
public class QuickSort {
private final static Random random = new Random();
public static void sort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* 快速排序
* @param arr 待排序数组
* @param l 左端点
* @param r 右端点
*/
private static void quickSort(int[] arr, int l, int r) {
if (l >= r) return;
// 随机选取基准值,让每次选取基准值更平均,避免出现极端情况
int point = (random.nextInt() % (r - l + 1)) + l;
swap(arr, l, point);
// 保证左边的数都小于等于基准,右边都大于基准
int i = l + 1, j = r;
// 左边比基准值大的 和 右边比基准值小的 交换
while (i < j) {
// 两边都拿到第一个满足条件的
while (i < j && arr[i] <= arr[l]) {
i++;
}
while (i < j && arr[j] > arr[l]) {
j--;
}
swap(arr, i, j);
}
if (arr[i] > arr[l]) {
i--;
}
swap(arr, i, l);
// 继续处理两边的
quickSort(arr, l, i);
quickSort(arr, i + 1, r);
}
private static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
归并排序
性能:时间复杂度O(nlogn) ,空间复杂度 O(n),稳定
思路:利用分治思想,把数组对半分,先把子数组排好序,最后合并结果。
代码如下:
java
public class MergeSort {
public static void sort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int l, int r) {
if (l >= r) return;
// 取中点,对两边进行排序
int mid = l + (r - l) / 2;
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
// 合并两个有序结果
merge(arr, l, mid, r);
}
private static void merge(int[] arr, int l, int m, int r) {
int[] temp = new int[r - l + 1];
int i = l, j = m + 1;
int k = 0;
while (i <= m && j <= r) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= m) temp[k++] = arr[i++];
while (j <= r) temp[k++] = arr[j++];
// 用合并好的覆盖掉之前的
System.arraycopy(temp, 0, arr, l, temp.length);
}
}
总结(deepseek)
