常见排序算法
目录
以下是一份使用Java实现的常见排序算法总结资料,包含了各种排序算法的实现代码、原理说明和复杂度分析:
1. 冒泡排序(Bubble Sort)
原理:重复比较相邻元素,将较大的元素逐步"冒泡"到数组末端
public class BubbleSort {
public static void sort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
// 标记本轮是否有交换,优化已排序情况
boolean swapped = false;
for (int j = 0; j < n - i - 1; 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²),最好情况O(n)(已排序时)
- 空间复杂度:O(1)
- 稳定性:稳定
2. 选择排序(Selection Sort)
原理:每次从剩余元素中找到最小值,放到已排序部分的末尾
public class SelectionSort {
public static void sort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
// 找到最小元素的索引
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换最小元素与当前位置元素
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
}
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定性:不稳定
3. 插入排序(Insertion Sort)
原理:构建有序序列,将未排序元素插入到已排序序列的合适位置
public class InsertionSort {
public static void sort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];//当前要插入的元素(未排序部分的第一个元素)
int j = i - 1;//j 从已排序部分的最后一个元素开始向前移动
// 将已排序的部分中大于key的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;//将 key 插入到找到的合适位置(j+1)
}
}
}
- 时间复杂度:O(n²),最好情况O(n)(已排序时)
- 空间复杂度:O(1)
- 稳定性:稳定
4. 希尔排序(Shell Sort)
原理:插入排序的改进版,通过分组进行排序,逐步减小间隔
public class ShellSort {
public static void sort(int[] arr) {
int n = arr.length;
// 初始间隔设为n/2,逐渐减小
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个间隔组进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];// 保存当前要插入的元素
int j;
// 移动同组中之前的元素
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
}
- 时间复杂度:O(n log n) ~ O(n²)(取决于间隔序列)
- 空间复杂度:O(1)
- 稳定性:不稳定
5. 归并排序(Merge Sort)
原理:分治法,将数组分成两半分别排序,再合并结果
public class MergeSort {
public static void sort(int[] arr) {
if (arr.length > 1) {
int mid = arr.length / 2;
// 分成两个子数组
int[] left = new int[mid];
int[] right = new int[arr.length - mid];
System.arraycopy(arr, 0, left, 0, mid);
System.arraycopy(arr, mid, right, 0, arr.length - mid);
// 递归排序子数组
sort(left);
sort(right);
// 合并排序后的子数组
merge(arr, left, right);
}
}
private static void merge(int[] result, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
// 合并两个有序数组
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result[k++] = left[i++];
} else {
result[k++] = right[j++];
}
}
// 复制剩余元素
while (i < left.length) {
result[k++] = left[i++];
}
while (j < right.length) {
result[k++] = right[j++];
}
}
}
- 时间复杂度:O(n log n)
- 空间复杂度:O(n)
- 稳定性:稳定
6. 快速排序(Quick Sort)
原理:选择基准元素,将数组分为小于和大于基准的两部分,递归排序
public class QuickSort {
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void quickSort(int[] arr, int start, int end) {
if (start >= end)
return;
int k = arr[start];
int i = start, j = end;
while (i != j) {
while (i < j && arr[j] >= k)
--j;
swap(arr, i, j);
while (i < j && arr[i] <= k)
++i;
swap(arr, i, j);
}
quickSort(arr, start, i - 1);
quickSort(arr, i + 1, end);
}
public static void main(String[] args) {
int[] arr = {5, 2, 6, 9, 1, 3, 4, 8, 7, 10};
new QuickSort().quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
- 时间复杂度:O(n log n),最坏情况O(n²)
- 空间复杂度:O(log n) ~ O(n)(递归栈)
- 稳定性:不稳定
7. 堆排序(Heap Sort)
原理:利用堆数据结构,先构建最大堆,再依次提取最大值
public class HeapSort {
public static void sort(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)
- 空间复杂度:O(1)
- 稳定性:不稳定
8. 计数排序(Counting Sort)
原理:基于整数范围,统计每个元素出现次数,再重建有序数组
public class CountingSort {
public static void sort(int[] arr) {
if (arr.length == 0) return;
// 找到数组中的最大值和最小值
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
// 创建计数数组
int range = max - min + 1;
int[] count = new int[range];
int[] output = new int[arr.length];
// 统计每个元素出现的次数
for (int num : arr) {
count[num - min]++;
}
// 计算累积计数
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 构建输出数组
for (int i = arr.length - 1; i >= 0; i--) {
output[count[arr[i] - min] - 1] = arr[i];
count[arr[i] - min]--;
}
// 复制结果到原数组
System.arraycopy(output, 0, arr, 0, arr.length);
}
}
- 时间复杂度:O(n + k),k是数据范围
- 空间复杂度:O(n + k)
- 稳定性:稳定
总结:排序算法选择建议
- 小规模数据或接近有序:插入排序
- 中等规模数据:快速排序或归并排序
- 要求稳定排序:归并排序、冒泡排序、插入排序、计数排序
- 内存受限场景:堆排序、希尔排序等原地排序算法
- 整数排序且范围不大:计数排序
每种排序算法都有其适用场景,实际应用中需根据数据规模、数据特性和性能要求选择合适的算法。