冒泡排序是最直观的排序方法。从头开始两两比较相邻元素,前面比后面大就交换,一趟下来最大的元素沉到了最后。重复n-1趟就排完了。加一个swapped标志可以提前结束,某一趟没有发生交换就说明已经有序了。
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break;
}
}
冒泡排序是稳定的,相等的元素不会交换。时间复杂度O(n²),适合数据量小的场景。
快速排序是分治思想的典型应用。每次选一个基准值,通过一趟扫描把比基准小的放左边,比基准大的放右边,基准就放到了它最终的位置上。然后递归处理左右两部分。
分区的过程:用i和j两个指针,j从左到右扫描,遇到比基准小的就和i位置交换,i后移。扫描完后i左边的都比基准小,i右边都比基准大,最后把基准放到i的位置。
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
快速排序平均O(n log n),但极端情况下会退化成O(n²),比如数组已经有序且基准总选最后一个。实际工程里会对基准选择做优化,比如三数取中。
堆排序利用了大顶堆的性质:父节点永远比子节点大。第一步把无序数组构建成大顶堆,从最后一个非叶子节点开始依次下沉。第二步反复把堆顶和末尾交换,然后调整剩余元素重新成堆。
建堆的复杂度是O(n),每次调整是O(log n),总复杂度O(n log n)。堆排序不稳定,因为跳跃式交换会打乱相等元素的相对顺序。
public static void heapSort(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
private static void heapify(int[] arr, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest != i) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
heapify(arr, n, largest);
}
}
三种排序怎么选:数据量小、要求稳定用冒泡。数据量大、随机性强用快排,它常数小,实际跑起来最快。不想让快排出现最坏情况,或者对空间有要求,堆排序更稳健。归并排序虽然也是O(n log n)且稳定,但要额外开空间,这里没展开。