排序算法是计算机科学中最基础和重要的算法之一。本文将详细介绍几种经典的排序算法,包括选择排序、插入排序、希尔排序、堆排序和归并排序,并进行代码实现和对比分析。
选择排序(Selection Sort)
选择排序的基本思想是每次从未排序部分选择最小的元素,将其与未排序部分的第一个元素交换,从而逐步建立有序序列。其实现代码如下:
public class SelectionSort {// 选择排序方法public static void selectionSort(int[] array) {int n = array.length;for (int i = 0; i < n - 1; i++) {// 找到未排序部分的最小元素int minIndex = i;for (int j = i + 1; j < n; j++) {if (array[j] < array[minIndex]) {minIndex = j;}}// 将最小元素与未排序部分的第一个元素交换int temp = array[minIndex];array[minIndex] = array[i];array[i] = temp;}}
}
插入排序(Insertion Sort)
插入排序的基本思想是将数组划分为已排序和未排序两部分,逐个将未排序部分的元素插入到已排序部分的适当位置。其实现代码如下:
public class InsertionSort {// 插入排序方法public static void insertionSort(int[] array) {for (int i = 1; i < array.length; i++) {int key = array[i];int j = i - 1;// 将当前元素插入到已排序部分的正确位置while (j >= 0 && array[j] > key) {array[j + 1] = array[j];j--;}array[j + 1] = key;}}
}
希尔排序(Shell Sort)
希尔排序是插入排序的改进版,通过将数组分成多个子数组分别进行插入排序,然后逐步减少子数组的规模,最终进行一次标准的插入排序。其实现代码如下,使用给定的增量序列 (h = 7, 3, 1):
public class ShellSort {// 希尔排序方法public static void shellSort(int[] array) {int n = array.length;// 使用给定的增量序列 h = 7, 3, 1int[] gaps = {7, 3, 1};for (int gap : gaps) {// 对每个增量序列进行排序for (int i = gap; i < n; i++) {int temp = array[i];int j = i;// 将 temp 插入到合适的位置while (j >= gap && array[j - gap] > temp) {array[j] = array[j - gap];j -= gap;}array[j] = temp;}}}
}
堆排序(Heap Sort)
堆排序的基本思想是将数组构建成一个堆,然后重复地将堆顶元素(最大元素)与未排序部分的最后一个元素交换,并将剩余部分重新调整为堆,直到整个数组有序。其实现代码如下:
public class HeapSort {// 堆排序方法public static void heapSort(int[] array) {int n = array.length;// 构建最大堆for (int i = n / 2 - 1; i >= 0; i--) {heapify(array, n, i);}// 一个个将堆顶元素与末尾元素交换,并重新调整堆for (int i = n - 1; i > 0; i--) {// 交换堆顶元素和末尾元素int temp = array[0];array[0] = array[i];array[i] = temp;// 重新调整堆heapify(array, i, 0);}}// 将以i为根的子树调整为最大堆private static void heapify(int[] array, int n, int i) {int largest = i; // 根节点int left = 2 * i + 1; // 左子节点int right = 2 * i + 2; // 右子节点// 如果左子节点比根节点大if (left < n && array[left] > array[largest]) {largest = left;}// 如果右子节点比目前最大的节点大if (right < n && array[right] > array[largest]) {largest = right;}// 如果最大的节点不是根节点if (largest != i) {int swap = array[i];array[i] = array[largest];array[largest] = swap;// 递归地调整受影响的子树heapify(array, n, largest);}}
}
归并排序(Merge Sort)
归并排序的基本思想是将数组分成两部分,分别进行排序,然后合并两个有序的子数组。其实现代码如下:
public class MergeSort {// 归并排序方法public static void mergeSort(int[] array) {if (array.length > 1) {int mid = array.length / 2;// 分成两个子数组int[] left = new int[mid];int[] right = new int[array.length - mid];System.arraycopy(array, 0, left, 0, mid);System.arraycopy(array, mid, right, 0, array.length - mid);// 递归排序两个子数组mergeSort(left);mergeSort(right);// 合并两个有序子数组merge(array, left, right);}}// 合并两个有序子数组private static void merge(int[] array, int[] left, int[] right) {int i = 0, j = 0, k = 0;while (i < left.length && j < right.length) {if (left[i] <= right[j]) {array[k++] = left[i++];} else {array[k++] = right[j++];}}while (i < left.length) {array[k++] = left[i++];}while (j < right.length) {array[k++] = right[j++];}}
}
算法对比分析
对这些排序算法的运行时间和空间复杂度进行对比分析如下:
排序算法 | 最好情况时间复杂度 | 最坏情况时间复杂度 | 空间复杂度 | 备注 |
---|---|---|---|---|
选择排序 | Θ(N²) | Θ(N²) | Θ(1) | 简单但效率低,适用于小规模数据集 |
堆排序(不使用多余空间) | Θ(N) | Θ(N log N) | Θ(1) | 相对较快,但在缓存性能方面表现较差 |
归并排序 | Θ(N log N) | Θ(N log N) | Θ(N) | 在大多数情况下最快,但需要额外的内存空间 |
插入排序(不使用多余空间) | Θ(N) | Θ(N²) | Θ(1) | 最适合小规模或近乎有序的数据集 |
希尔排序 | Θ(N) | Ω(N log N) O(?) | Θ(1) | 理论丰富,性能视增量序列而定 |
结论
- 选择排序:简单直观,但在处理大规模数据时效率低下,适合小规模数据集。
- 插入排序:在数据几乎有序或规模较小时非常高效。
- 希尔排序:通过增量序列优化插入排序,性能依赖于具体的增量序列选择。
- 堆排序:时间复杂度较低,特别是在最坏情况下,但在处理大规模数据时,缓存性能表现不佳。
- 归并排序:在大多数情况下效率最高,但需要额外的内存空间。