算法概览与性能对比
| 排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 希尔排序 | O(n^1.3-1.5) | O(n) | O(n²) | O(1) | 不稳定 |
| 堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 计数排序 | O(n + k) | O(n + k) | O(n + k) | O(k) | 稳定 |
一、冒泡排序 (Bubble Sort)
算法思想
通过相邻元素的比较和交换,使较大的元素逐渐"浮"到数组末端。
代码实现
java
public class BubbleSort {
/**
* 基础冒泡排序
*/
public void bubbleSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) { // 改为升序
swap(arr, j, j + 1);
}
}
}
System.out.println("冒泡排序: " + Arrays.toString(arr));
}
/**
* 优化版冒泡排序 - 添加标志位检测是否已有序
*/
public void bubbleSortOptimized(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
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]) {
swap(arr, j, j + 1);
swapped = true;
}
}
// 如果本轮没有发生交换,说明数组已有序
if (!swapped) break;
}
System.out.println("优化冒泡: " + Arrays.toString(arr));
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
算法特点
- 稳定排序
- 适合小规模数据
- 优化后对基本有序数组效果较好
二、插入排序 (Insertion Sort)
算法思想
将数组分为已排序和未排序两部分,每次将未排序部分的第一个元素插入到已排序部分的正确位置。
代码实现
java
public class InsertionSort {
/**
* 直接插入排序
*/
public void insertionSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i]; // 当前要插入的元素
int j = i - 1;
// 将比key大的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key; // 插入到正确位置
}
System.out.println("插入排序: " + Arrays.toString(arr));
}
/**
* 带预检查的插入排序
*/
public void insertionSortWithCheck(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
for (int i = 1; i < n; i++) {
// 如果当前元素已经大于等于前一个元素,不需要插入
if (arr[i] >= arr[i - 1]) continue;
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
System.out.println("优化插入: " + Arrays.toString(arr));
}
}
算法特点
- 稳定排序
- 对基本有序数组效率很高
- 适合小规模数据
三、选择排序 (Selection Sort)
算法思想
每次从未排序部分选择最小(或最大)元素,放到已排序部分的末尾。
代码实现
java
public class SelectionSort {
/**
* 选择排序
*/
public void selectionSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
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;
}
}
// 将最小元素交换到当前位置
if (minIndex != i) {
swap(arr, i, minIndex);
}
}
System.out.println("选择排序: " + Arrays.toString(arr));
}
/**
* 双向选择排序 - 同时找到最小和最大元素
*/
public void selectionSortBidirectional(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
int left = 0, right = n - 1;
while (left < right) {
int minIndex = left, maxIndex = right;
// 确保minIndex <= maxIndex
if (arr[minIndex] > arr[maxIndex]) {
swap(arr, minIndex, maxIndex);
}
// 在剩余部分寻找最小和最大值
for (int i = left + 1; i < right; i++) {
if (arr[i] < arr[minIndex]) {
minIndex = i;
} else if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
// 将最小值放到左边,最大值放到右边
swap(arr, left, minIndex);
swap(arr, right, maxIndex);
left++;
right--;
}
System.out.println("双向选择: " + Arrays.toString(arr));
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
算法特点
- 不稳定排序
- 交换次数较少
- 适合数据量较小的情况
四、希尔排序 (Shell Sort)
算法思想
是插入排序的改进版,通过分组进行插入排序,逐渐减小分组间隔,最终完成排序。
代码实现
java
public class ShellSort {
/**
* 希尔排序
*/
public void shellSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
// 使用Knuth序列计算间隔
int gap = 1;
while (gap < n / 3) {
gap = gap * 3 + 1;
}
while (gap > 0) {
// 对每个间隔进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j = i;
// 对间隔为gap的元素进行插入排序
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
gap /= 3; // 减小间隔
}
System.out.println("希尔排序: " + Arrays.toString(arr));
}
/**
* 使用二分法优化的希尔排序
*/
public void shellSortOptimized(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
// 使用二分法确定间隔序列
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个间隔进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j = i;
// 移动元素,为temp找到正确位置
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
System.out.println("优化希尔: " + Arrays.toString(arr));
}
}
算法特点
- 不稳定排序
- 插入排序的高效改进版
- 间隔序列的选择影响性能
五、堆排序 (Heap Sort)
算法思想
利用堆这种数据结构进行排序,建立大根堆后,不断将堆顶元素与末尾元素交换并调整堆。
代码实现
java
public class HeapSort {
/**
* 堆排序
*/
public void heapSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
// 构建大根堆
buildMaxHeap(arr);
// 逐个提取最大元素
for (int i = n - 1; i > 0; i--) {
// 将堆顶元素(最大)与当前末尾元素交换
swap(arr, 0, i);
// 调整剩余元素为堆
heapify(arr, 0, i);
}
System.out.println("堆排序: " + Arrays.toString(arr));
}
/**
* 构建大根堆
*/
private void buildMaxHeap(int[] arr) {
int n = arr.length;
// 从最后一个非叶子节点开始向上调整
for (int i = (n - 2) / 2; i >= 0; i--) {
heapify(arr, i, n);
}
}
/**
* 堆调整(大根堆)
*/
private void heapify(int[] arr, int parent, int heapSize) {
int largest = parent;
int left = 2 * parent + 1;
int right = 2 * parent + 2;
// 找出父节点、左孩子、右孩子中的最大值
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是父节点,交换并继续调整
if (largest != parent) {
swap(arr, parent, largest);
heapify(arr, largest, heapSize);
}
}
/**
* 非递归版本的堆调整
*/
private void heapifyIterative(int[] arr, int parent, int heapSize) {
int temp = arr[parent];
for (int child = 2 * parent + 1; child < heapSize; child = 2 * child + 1) {
// 选择较大的子节点
if (child + 1 < heapSize && arr[child] < arr[child + 1]) {
child++;
}
// 如果父节点已经大于子节点,调整完成
if (temp >= arr[child]) break;
// 将子节点值赋给父节点
arr[parent] = arr[child];
parent = child;
}
arr[parent] = temp;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
算法特点
- 不稳定排序
- 时间复杂度稳定在O(n log n)
- 不需要额外空间
六、快速排序 (Quick Sort)
算法思想
采用分治策略,选择一个基准元素,将数组分为两部分,递归排序。
代码实现
java
public class QuickSort {
/**
* 快速排序(递归版本)
*/
public void quickSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
quickSortRecursive(arr, 0, arr.length - 1);
System.out.println("快速排序: " + Arrays.toString(arr));
}
private void quickSortRecursive(int[] arr, int low, int high) {
if (low < high) {
// 分区操作,返回基准位置
int pivot = partition(arr, low, high);
// 递归排序左右子数组
quickSortRecursive(arr, low, pivot - 1);
quickSortRecursive(arr, pivot + 1, high);
}
}
/**
* 分区函数 - 单边扫描法
*/
private 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++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
/**
* 三数取中法优化 - 选择更好的基准
*/
public void quickSortOptimized(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
quickSortOptimizedRecursive(arr, 0, arr.length - 1);
System.out.println("优化快排: " + Arrays.toString(arr));
}
private void quickSortOptimizedRecursive(int[] arr, int low, int high) {
if (low < high) {
// 三数取中选择基准
medianOfThree(arr, low, high);
int pivot = partition(arr, low, high);
quickSortOptimizedRecursive(arr, low, pivot - 1);
quickSortOptimizedRecursive(arr, pivot + 1, high);
}
}
/**
* 三数取中法
*/
private void medianOfThree(int[] arr, int low, int high) {
int mid = low + (high - low) / 2;
// 确保arr[low] <= arr[mid] <= arr[high]
if (arr[low] > arr[mid]) swap(arr, low, mid);
if (arr[low] > arr[high]) swap(arr, low, high);
if (arr[mid] > arr[high]) swap(arr, mid, high);
// 将中位数放到high位置作为基准
swap(arr, mid, high);
}
/**
* 快速排序(非递归版本)
*/
public void quickSortIterative(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
Stack<Integer> stack = new Stack<>();
stack.push(0);
stack.push(n - 1);
while (!stack.isEmpty()) {
int high = stack.pop();
int low = stack.pop();
if (low < high) {
int pivot = partition(arr, low, high);
// 将左子数组边界压栈
if (pivot - 1 > low) {
stack.push(low);
stack.push(pivot - 1);
}
// 将右子数组边界压栈
if (pivot + 1 < high) {
stack.push(pivot + 1);
stack.push(high);
}
}
}
System.out.println("非递归快排: " + Arrays.toString(arr));
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
算法特点
- 不稳定排序
- 平均性能很好
- 对基准选择敏感
七、归并排序 (Merge Sort)
算法思想
采用分治策略,将数组递归分成两半分别排序,然后合并两个有序数组。
代码实现
java
public class MergeSort {
/**
* 归并排序
*/
public void mergeSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
mergeSortRecursive(arr, 0, arr.length - 1);
System.out.println("归并排序: " + Arrays.toString(arr));
}
private void mergeSortRecursive(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// 递归排序左右子数组
mergeSortRecursive(arr, left, mid);
mergeSortRecursive(arr, mid + 1, right);
// 合并两个有序子数组
merge(arr, left, mid, right);
}
}
/**
* 合并两个有序子数组
*/
private void merge(int[] arr, int left, int mid, int right) {
// 创建临时数组
int[] temp = new int[right - left + 1];
int i = left; // 左子数组起始索引
int j = mid + 1; // 右子数组起始索引
int k = 0; // 临时数组索引
// 合并两个有序数组
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
// 复制左子数组剩余元素
while (i <= mid) {
temp[k++] = arr[i++];
}
// 复制右子数组剩余元素
while (j <= right) {
temp[k++] = arr[j++];
}
// 将临时数组复制回原数组
System.arraycopy(temp, 0, arr, left, temp.length);
}
/**
* 归并排序(非递归版本)
*/
public void mergeSortIterative(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
// 从大小为1的子数组开始合并
for (int currSize = 1; currSize < n; currSize *= 2) {
// 合并所有成对的子数组
for (int left = 0; left < n - 1; left += 2 * currSize) {
int mid = Math.min(left + currSize - 1, n - 1);
int right = Math.min(left + 2 * currSize - 1, n - 1);
merge(arr, left, mid, right);
}
}
System.out.println("非递归归并: " + Arrays.toString(arr));
}
/**
* 优化版归并排序 - 避免小数组时的递归
*/
public void mergeSortOptimized(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
int n = arr.length;
int[] temp = new int[n]; // 一次性分配临时数组
mergeSortOptimizedRecursive(arr, 0, n - 1, temp);
System.out.println("优化归并: " + Arrays.toString(arr));
}
private void mergeSortOptimizedRecursive(int[] arr, int left, int right, int[] temp) {
// 小数组使用插入排序
if (right - left < 15) {
insertionSort(arr, left, right);
return;
}
if (left < right) {
int mid = left + (right - left) / 2;
mergeSortOptimizedRecursive(arr, left, mid, temp);
mergeSortOptimizedRecursive(arr, mid + 1, right, temp);
// 如果已经有序,不需要合并
if (arr[mid] <= arr[mid + 1]) return;
mergeWithTemp(arr, left, mid, right, temp);
}
}
private void mergeWithTemp(int[] arr, int left, int mid, int right, int[] temp) {
System.arraycopy(arr, left, temp, left, right - left + 1);
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (temp[i] <= temp[j]) {
arr[k++] = temp[i++];
} else {
arr[k++] = temp[j++];
}
}
while (i <= mid) arr[k++] = temp[i++];
while (j <= right) arr[k++] = temp[j++];
}
private void insertionSort(int[] arr, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int key = arr[i];
int j = i - 1;
while (j >= left && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
}
算法特点
- 稳定排序
- 时间复杂度稳定
- 需要额外空间
八、计数排序 (Counting Sort)
算法思想
非比较排序,通过统计每个元素出现的次数,然后计算每个元素在输出数组中的位置。
代码实现
java
public class CountingSort {
/**
* 计数排序 - 基础版本
*/
public void countingSort(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
if (arr.length == 0) return;
// 找出数组中的最大值和最小值
int max = arr[0], min = arr[0];
for (int num : arr) {
if (num > max) max = num;
if (num < min) min = num;
}
// 创建计数数组
int range = max - min + 1;
int[] count = new int[range];
// 统计每个元素出现的次数
for (int num : arr) {
count[num - min]++;
}
// 重构原数组
int index = 0;
for (int i = 0; i < range; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
System.out.println("计数排序: " + Arrays.toString(arr));
}
/**
* 计数排序 - 稳定版本
*/
public void countingSortStable(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
if (arr.length == 0) return;
// 找出数组中的最大值和最小值
int max = arr[0], min = arr[0];
for (int num : arr) {
if (num > max) max = num;
if (num < min) min = 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 < range; 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);
System.out.println("稳定计数: " + Arrays.toString(arr));
}
/**
* 适用于大范围数据的计数排序
*/
public void countingSortForLargeRange(int[] array) {
int[] arr = Arrays.copyOf(array, array.length);
if (arr.length == 0) return;
// 使用哈希思想处理大范围数据
Map<Integer, Integer> countMap = new TreeMap<>();
// 统计频率
for (int num : arr) {
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
}
// 重构数组
int index = 0;
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
int num = entry.getKey();
int frequency = entry.getValue();
for (int i = 0; i < frequency; i++) {
arr[index++] = num;
}
}
System.out.println("大范围计数: " + Arrays.toString(arr));
}
}
算法特点
- 稳定排序(稳定版本)
- 非比较排序
- 适合数据范围不大的情况
综合测试类
java
public class SortingTest {
public static void main(String[] args) {
int[] testArray = {64, 34, 25, 12, 22, 11, 90, 5, 77, 30};
System.out.println("原始数组: " + Arrays.toString(testArray));
System.out.println();
// 测试各种排序算法
BubbleSort bubble = new BubbleSort();
bubble.bubbleSort(testArray);
bubble.bubbleSortOptimized(testArray);
InsertionSort insertion = new InsertionSort();
insertion.insertionSort(testArray);
insertion.insertionSortWithCheck(testArray);
SelectionSort selection = new SelectionSort();
selection.selectionSort(testArray);
selection.selectionSortBidirectional(testArray);
ShellSort shell = new ShellSort();
shell.shellSort(testArray);
shell.shellSortOptimized(testArray);
HeapSort heap = new HeapSort();
heap.heapSort(testArray);
QuickSort quick = new QuickSort();
quick.quickSort(testArray);
quick.quickSortOptimized(testArray);
quick.quickSortIterative(testArray);
MergeSort merge = new MergeSort();
merge.mergeSort(testArray);
merge.mergeSortIterative(testArray);
merge.mergeSortOptimized(testArray);
CountingSort counting = new CountingSort();
counting.countingSort(testArray);
counting.countingSortStable(testArray);
// 测试大范围数据
int[] largeRangeArray = {1000, 5, 200, 50, 1000, 5, 200};
counting.countingSortForLargeRange(largeRangeArray);
}
}
算法选择指南
- 小规模数据 (n < 50) : 插入排序、选择排序
- 基本有序数据: 插入排序、冒泡排序
- 大规模数据: 快速排序、归并排序、堆排序
- 数据范围有限: 计数排序
- 需要稳定性: 归并排序、插入排序
- 内存有限: 堆排序、快速排序
每种排序算法都有其适用场景,理解它们的原理和特性有助于在实际问题中选择最合适的算法。