马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
排序算法不管是考试、口试、还是一样寻常开辟中都是一个特别高频的点。下面对八种排序算法做简朴的介绍。
1. 冒泡排序(Bubble Sort)
原理:相邻元素比较,每一轮将最大元素“冒泡”到末端。
示例数组:[5, 3, 8, 1, 2]- public static void bubbleSort(int[] arr) {
- for (int i = 0; i < arr.length - 1; i++) {
- boolean swapped = false;
- for (int j = 0; j < arr.length - i - 1; j++) {
- if (arr[j] > arr[j + 1]) {
- int temp = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = temp;
- swapped = true;
- }
- }
- System.out.println("第 " + (i + 1) + " 轮后: " + Arrays.toString(arr));
- if (!swapped) break;
- }
- }
复制代码 输出:- 第 1 轮后: [3, 5, 1, 2, 8]
- 第 2 轮后: [3, 1, 2, 5, 8]
- 第 3 轮后: [1, 2, 3, 5, 8]
- 第 4 轮后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
- 最好情况:O(n)(已有序时一轮扫描结束)
- 最坏/均匀:O(n²)(双重循环嵌套)
空间复杂度:O(1)(原地排序,仅需常数空间)
盘算原理:
- 外层循环最多实验 n-1 次,内层循环每次实验 n-i-1 次
- 总比较次数 ≈ (n-1) + (n-2) + … + 1 = n(n-1)/2 → O(n²)
2. 选择排序(Selection Sort)
原理:每轮选择最小的元素,放到已排序部分的末端。
示例数组:[5, 3, 8, 1, 2]- public static void selectionSort(int[] arr) {
- for (int i = 0; i < arr.length - 1; i++) {
- int minIndex = i;
- for (int j = i + 1; j < arr.length; j++) {
- if (arr[j] < arr[minIndex]) minIndex = j;
- }
- int temp = arr[i];
- arr[i] = arr[minIndex];
- arr[minIndex] = temp;
- System.out.println("第 " + (i + 1) + " 轮后: " + Arrays.toString(arr));
- }
- }
复制代码 输出:- 第 1 轮后: [1, 3, 8, 5, 2]
- 第 2 轮后: [1, 2, 8, 5, 3]
- 第 3 轮后: [1, 2, 3, 5, 8]
- 第 4 轮后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
空间复杂度:O(1)(原地互换)
盘算原理:
- 外层循环 n-1 次,内层循环每次实验 n-i-1 次
- 总比较次数 = (n-1) + (n-2) + … + 1 = n(n-1)/2 → O(n²)
3. 插入排序(Insertion Sort)
原理:将未排序元素插入已排序部分的精确位置。
示例数组:[5, 3, 8, 1, 2]- public static void insertionSort(int[] arr) {
- for (int i = 1; i < arr.length; i++) {
- 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("插入 " + key + " 后: " + Arrays.toString(arr));
- }
- }
复制代码 输出:- 插入 3 后: [3, 5, 8, 1, 2]
- 插入 8 后: [3, 5, 8, 1, 2]
- 插入 1 后: [1, 3, 5, 8, 2]
- 插入 2 后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
- 最好:O(n)(已有序时仅需线性扫描)
- 最坏/均匀:O(n²)(每次插入需移动大量元素)
空间复杂度:O(1)
盘算原理:
- 外层循环 n-1 次,内层循环均匀移动 i/2 次
- 总操作次数 ≈ 1 + 2 + … + (n-1) = n(n-1)/2 → O(n²)
4. 快速排序(Quick Sort)
原理:通过基准分区,递归排序左右子数组。
示例数组:[5, 3, 8, 1, 2]- public static void quickSort(int[] arr, int low, int high) {
- if (low < high) {
- int pi = partition(arr, low, high);
- System.out.println("基准 " + arr[pi] + " 分区后: " + Arrays.toString(arr));
- 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;
- }
复制代码 输出:- 基准 2 分区后: [1, 2, 8, 5, 3]
- 基准 1 分区后: [1, 2, 8, 5, 3]
- 基准 3 分区后: [1, 2, 3, 5, 8]
- 基准 8 分区后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
- 最好/均匀:O(n log n)(每次分区将数组分成两半)
- 最坏:O(n²)(每次分区极不均衡,如已有序数组)
空间复杂度:O(log n)(递归调用栈的深度)
盘算原理:
- 理想情况:每次分区后左右子数组巨细相称,递归深度 log n,每层处理处罚 O(n) → O(n log n)
- 最坏情况:每次分区后一个子数组为空,递归深度 n → O(n²)
5. 归并排序(Merge Sort)
原理:分治法,将数组拆分为两半,归并有序子数组。
示例数组:[5, 3, 8, 1, 2]- public static void mergeSort(int[] arr, int left, int right) {
- if (left < right) {
- int mid = (left + right) / 2;
- mergeSort(arr, left, mid);
- mergeSort(arr, mid + 1, right);
- merge(arr, left, mid, right);
- System.out.println("合并后: " + Arrays.toString(arr));
- }
- }
- private static void merge(int[] arr, int left, int mid, int right) {
- int[] temp = new int[right - left + 1];
- int i = left, j = mid + 1, 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);
- }
复制代码 输出:- 合并后: [3, 5, 8, 1, 2]
- 合并后: [3, 5, 1, 8, 2]
- 合并后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
空间复杂度:O(n)(归并时需要暂时数组)
盘算原理:
- 递归树深度为 log n,每层归并总时间 O(n) → O(n log n)
- 归并操作需要额外空间存储暂时数组
6. 堆排序(Heap Sort)
原理:构建最大堆,依次将堆顶元素与末端互换。
示例数组:[5, 3, 8, 1, 2]- 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;
- System.out.println("交换堆顶后: " + Arrays.toString(arr));
- heapify(arr, i, 0);
- }
- }
- private static void heapify(int[] arr, int n, int i) {
- int largest = i, left = 2 * i + 1, 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);
- }
- }
复制代码 输出:- 交换堆顶后: [5, 3, 2, 1, 8]
- 交换堆顶后: [1, 3, 2, 5, 8]
- 交换堆顶后: [2, 1, 3, 5, 8]
- 交换堆顶后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
- 所有情况:O(n log n)(建堆 O(n),每次调解堆 O(log n))
空间复杂度:O(1)(原地排序)
盘算原理:
- 建堆时间复杂度为 O(n)(非叶子节点下沉操作)
- 每次互换堆顶后调解堆需要 O(log n) 时间,共 n-1 次 → O(n log n)
7. 希尔排序(Shell Sort)
原理:按间隔分组,渐渐缩小间隔举行插入排序。
示例数组:[5, 3, 8, 1, 2]- public static void shellSort(int[] arr) {
- int n = arr.length;
- for (int gap = n / 2; gap > 0; gap /= 2) {
- for (int i = gap; i < n; i++) {
- int temp = arr[i], j;
- for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
- arr[j] = arr[j - gap];
- }
- arr[j] = temp;
- }
- System.out.println("间隔 " + gap + " 排序后: " + Arrays.toString(arr));
- }
- }
复制代码 输出:- 间隔 2 排序后: [5, 1, 2, 3, 8]
- 间隔 1 排序后: [1, 2, 3, 5, 8]
复制代码 时间复杂度:
- 取决于间隔序列:
- 最坏 O(n²)(如使用原始希尔序列)
- 均匀 O(n log n)(如使用Knuth序列)
空间复杂度:O(1)
盘算原理:
- 通过分组淘汰数据移动间隔
- 差别间隔序列的时间复杂度差别,例如:
- 希尔原始序列(n/2^k):O(n²)
- Knuth序列(3k-1):O(n1.5)
8. 基数排序(Radix Sort)
原理:按位数从低到高排序(需稳固排序辅助)。
示例数组:[170, 45, 75, 90, 802, 24, 2, 66]- public static void radixSort(int[] arr) {
- int max = Arrays.stream(arr).max().getAsInt();
- for (int exp = 1; max / exp > 0; exp *= 10) {
- countingSort(arr, exp);
- System.out.println("按第 " + exp + " 位排序后: " + Arrays.toString(arr));
- }
- }
- private static void countingSort(int[] arr, int exp) {
- int[] output = new int[arr.length];
- int[] count = new int[10];
- for (int num : arr) count[(num / exp) % 10]++;
- for (int i = 1; i < 10; i++) count[i] += count[i - 1];
- for (int i = arr.length - 1; i >= 0; i--) {
- output[count[(arr[i] / exp) % 10] - 1] = arr[i];
- count[(arr[i] / exp) % 10]--;
- }
- System.arraycopy(output, 0, arr, 0, arr.length);
- }
复制代码 输出:- 按第 1 位排序后: [170, 90, 802, 2, 24, 45, 75, 66]
- 按第 10 位排序后: [802, 2, 24, 45, 66, 170, 75, 90]
- 按第 100 位排序后: [2, 24, 45, 66, 75, 90, 170, 802]
复制代码 时间复杂度:O(nk)(k为最大数字位数)
空间复杂度:O(n + k)(计数排序的额外空间)
盘算原理:
- 对每个位数举行稳固排序(如计数排序)
- 若最大数为 d 位,则举行 d 轮排序,每轮 O(n) → 总时间 O(dn)
总结表格
排序算法最好时间均匀时间最坏时间空间复杂度稳固性冒泡排序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 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 log n)O(n log n)O(n log n)O(1)不稳固希尔排序O(n log n)取决于间隔O(n²)O(1)不稳固基数排序O(nk)O(nk)O(nk)O(n + k)稳固复杂度盘算核心头脑
- 循环嵌套次数:
- 双重循环(如冒泡、选择、插入)→ O(n²)
- 分治法(快速、归并)→ 递归深度 log n × 每层 O(n) → O(n log n)
- 数据移动代价:
- 插入排序的移动次数与逆序对数量相关
- 堆排序的调解代价与树高度 log n 相关
- 额外空间使用:
- 递归算法的栈空间(快速排序 O(log n))
- 归并排序的暂时数组(O(n))
- 基数排序的计数数组(O(n + k))
|