《面试封神!八大排序算法:原理 + 实现 + 复杂度》

    1. 排序基础概念与核心指标
    • 1.1 什么是排序
    • 1.2 稳定性详解
    • 1.3 内部排序 vs 外部排序
    1. 插入类排序
    • 2.1 直接插入排序
    • 2.2 希尔排序(缩小增量排序)
    1. 选择类排序
    • 3.1 直接选择排序
    • 3.2 堆排序
    1. 交换类排序
    • 4.1 冒泡排序
    • 4.2 快速排序(递归 + 非递归 + 优化)
    1. 归并排序(递归 + 非递归)
    1. 非比较类排序:计数排序
    1. 所有排序复杂度 + 稳定性终极对比表
    1. 面试高频真题 + 答案解析

1. 排序基础概念与核心指标

1.1 排序定义

将一组数据按照关键字大小,递增或递减排列的操作。

1.2 稳定性(面试必考)

  • 稳定排序:相等元素在排序后相对位置不变
  • 不稳定排序:相等元素在排序后相对位置可能改变

意义:多关键字排序(如先按成绩再按学号)必须保证稳定。

1.3 内部排序 vs 外部排序

  • 内部排序:数据全部放内存,速度快
  • 外部排序:数据太大无法全放入内存,需要磁盘交互(常用归并排序)

2. 插入类排序

2.1 直接插入排序

核心思想

像摸扑克牌一样,把当前数字插入到前面已经排好序的区间里。

复杂度深度分析

  • 最好时间复杂度:O (N)(数组已有序,只需遍历一遍)
  • 最坏 / 平均时间复杂度:O (N²)(逆序时,每个数都要挪到底)
  • 空间复杂度:O (1)(仅用临时变量,无额外空间)
  • 稳定性:稳定
  • 适用场景:数据量小、数据基本有序

Java 实现代码

public static void insertSort(int[] array) {
    for (int i = 1; i < array.length; i++) {
        int tmp = array[i];
        int j = i - 1;
        for (; j >= 0; j--) {
            if(array[j] > tmp) {
                array[j + 1] = array[j];
            }else {
                break;
            }
        }
        array[j + 1] = tmp;
    }
}

2.2 希尔排序(缩小增量排序)

核心思想

分组预排序 → 逐步缩小增量 → 最后执行直接插入排序,是直接插入的高效优化版

复杂度深度分析

  • 时间复杂度:O (N^1.3) ~ O (N^1.5)(和增量序列相关,无法精确数学计算)
  • 空间复杂度:O(1)
  • 稳定性:不稳定(分组跳跃交换会破坏相对位置)

Java 实现代码

public static void shellSort(int[] array) {
    int gap = array.length;
    while (gap > 1) {
        gap /= 2;
        shell(array, gap);
    }
}

public static void shell(int[] array, int gap) {
    for (int i = gap; i < array.length; i++) {
        int tmp = array[i];
        int j = i - gap;
        for (; j >= 0; j -= gap) {
            if(array[j] > tmp) {
                array[j + gap] = array[j];
            }else {
                break;
            }
        }
        array[j + gap] = tmp;
    }
}

3. 选择类排序

3.1 直接选择排序

核心思想

每一轮选出最小值,放到已排序区间末尾。

复杂度深度分析

  • 时间复杂度:最好、最坏、平均都是 O (N²)(无论有序与否都要完整比较)
  • 空间复杂度:O(1)
  • 稳定性:不稳定(交换会打乱相同元素顺序)

Java 实现代码

public static void selectSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int minIndex = i;
        for (int j = i + 1; j < array.length; j++) {
            if(array[j] < array[minIndex]) {
                minIndex = j;
            }
        }
        swap(array, i, minIndex);
    }
}

private static void swap(int[] array, int i, int j) {
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

3.2 堆排序

核心思想

利用大顶堆 / 小顶堆高效选择最大值,升序建大堆,降序建小堆。

复杂度深度分析

  • 时间复杂度:最好、最坏、平均都是 O (N log N)(建堆 O (N),调整 O (N log N))
  • 空间复杂度:O (1)(原地排序,无递归栈 / 额外数组)
  • 稳定性:不稳定

Java 实现代码

public static void heapSort(int[] array) {
    createHeap(array);
    int end = array.length - 1;
    while (end > 0) {
        swap(array, 0, end);
        siftDown(array, 0, end);
        end--;
    }
}

private static void createHeap(int[] array) {
    for (int parent = (array.length - 2) / 2; parent >= 0; parent--) {
        siftDown(array, parent, array.length);
    }
}

private static void siftDown(int[] array, int parent, int len) {
    int child = 2 * parent + 1;
    while (child < len) {
        if (child + 1 < len && array[child] < array[child + 1]) {
            child++;
        }
        if (array[child] > array[parent]) {
            swap(array, child, parent);
            parent = child;
            child = 2 * parent + 1;
        } else {
            break;
        }
    }
}

4. 交换类排序

4.1 冒泡排序

核心思想

相邻两两比较交换,大数 “沉底”,小数 “上浮”。

复杂度深度分析

  • 最好时间复杂度:O (N)(加标志位,有序时直接退出)
  • 最坏 / 平均时间复杂度:O(N²)
  • 空间复杂度:O(1)
  • 稳定性:稳定

Java 实现代码

public static void bubbleSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        boolean flg = false;
        for (int j = 0; j < array.length - 1 - i; j++) {
            if(array[j] > array[j + 1]) {
                swap(array, j, j + 1);
                flg = true;
            }
        }
        if(!flg) {
            return;
        }
    }
}

4.2 快速排序

核心思想

分治法:选基准 → 分区 → 递归左右。工程常用优化:三数取中小数据用插入排序非递归实现

复杂度深度分析

  • 最好 / 平均时间复杂度:O (N log N)(每次均分,递归树高度 log N)
  • 最坏时间复杂度:O (N²)(数据已有序,退化为链表)
  • 空间复杂度:O (log N) ~ O (N)(递归调用栈)
  • 稳定性:不稳定

Java 实现(递归 + 非递归)

// 递归快排
public static void quickSort(int[] array) {
    quick(array, 0, array.length - 1);
}

private static void quick(int[] array, int start, int end) {
    if(start >= end) return;
    // 小范围使用插入排序优化
    if(end - start + 1 <= 8) {
        insertSortRange(array, start, end);
        return;
    }
    // 三数取中优化
    int index = threeMid(array, start, end);
    swap(array, start, index);
    int par = parttion(array, start, end);
    quick(array, start, par - 1);
    quick(array, par + 1, end);
}

// 非递归快排
public static void quickSortNor(int[] array) {
    Deque<Integer> stack = new LinkedList<>();
    int start = 0;
    int end = array.length - 1;
    stack.push(start);
    stack.push(end);
    while (!stack.isEmpty()) {
        end = stack.pop();
        start = stack.pop();
        int par = parttion2(array, start, end);
        if(par > start + 1) {
            stack.push(start);
            stack.push(par - 1);
        }
        if(par < end - 1) {
            stack.push(par + 1);
            stack.push(end);
        }
    }
}

// 前后指针分区
private static int parttion(int[] array, int left, int right) {
    int prev = left;
    int cur = left + 1;
    while (cur <= right) {
        if(array[cur] < array[left] && array[++prev] != array[cur]) {
            swap(array, cur, prev);
        }
        cur++;
    }
    swap(array, prev, left);
    return prev;
}

5. 归并排序

核心思想

分治法:分解 → 排序子段 → 归并。最适合外部排序 / 海量数据排序

复杂度深度分析

  • 时间复杂度:最好、最坏、平均都是 O (N log N)
  • 空间复杂度:O (N)(需要临时数组存归并结果)
  • 稳定性:稳定(唯一 O (N log N) 稳定排序)

Java 实现(递归 + 非递归)

// 递归归并
public static void mergeSort1(int[] array) {
    mergeSortChild(array, 0, array.length - 1);
}

private static void mergeSortChild(int[] array, int left, int right) {
    if(left >= right) return;
    int mid = (left + right) / 2;
    mergeSortChild(array, left, mid);
    mergeSortChild(array, mid + 1, right);
    merge(array, left, mid, right);
}

// 非递归归并
public static void mergeSort(int[] array) {
    int gap = 1;
    while (gap < array.length) {
        for (int i = 0; i < array.length; i += 2 * gap) {
            int left = i;
            int mid = left + gap - 1;
            mid = Math.min(mid, array.length - 1);
            int right = mid + gap;
            right = Math.min(right, array.length - 1);
            merge(array, left, mid, right);
        }
        gap *= 2;
    }
}

// 归并核心
private static void merge(int[] array, int left, int mid, int right) {
    int[] tmp = new int[right - left + 1];
    int k = 0;
    int s1 = left;
    int s2 = mid + 1;
    while (s1 <= mid && s2 <= right) {
        if(array[s1] <= array[s2]) {
            tmp[k++] = array[s1++];
        }else {
            tmp[k++] = array[s2++];
        }
    }
    while (s1 <= mid) tmp[k++] = array[s1++];
    while (s2 <= right) tmp[k++] = array[s2++];
    for (int i = 0; i < tmp.length; i++) {
        array[i + left] = tmp[i];
    }
}

6. 非比较类排序:计数排序

核心思想

不比较元素,只统计次数,再按计数回填数组。

复杂度深度分析

  • 时间复杂度:O (N + 数据范围)
  • 空间复杂度:O (数据范围)
  • 稳定性:稳定
  • 适用场景:分数、年龄、频次统计(范围小且集中)

Java 实现代码

public static void countSort(int[] array) {
    int max = array[0];
    int min = array[0];
    for (int num : array) {
        max = Math.max(max, num);
        min = Math.min(min, num);
    }
    int range = max - min + 1;
    int[] count = new int[range];
    for (int num : array) {
        count[num - min]++;
    }
    int k = 0;
    for (int i = 0; i < count.length; i++) {
        while (count[i] > 0) {
            array[k++] = i + min;
            count[i]--;
        }
    }
}

7. 排序算法终极对比表(面试直接背)

表格

排序算法最好时间最坏时间空间稳定性
直接插入O(N)O(N²)O(1)✅ 稳定
希尔O(N)O(N²)O(1)❌ 不稳定
直接选择O(N²)O(N²)O(1)❌ 不稳定
堆排序O(N log N)O(N log N)O(1)❌ 不稳定
冒泡O(N)O(N²)O(1)✅ 稳定
快速排序O(N log N)O(N²)O(log N)❌ 不稳定
归并排序O(N log N)O(N log N)O(N)✅ 稳定
计数排序O (N + 范围)O (N + 范围)O (范围)✅ 稳定

8. 面试高频真题(含答案)

  1. 快速排序基于什么思想?分治法
  2. 稳定且时间复杂度 O (N²) 的排序?冒泡、直接插入
  3. 占用 O (N) 辅助空间的排序是?归并排序
  4. 数据基本有序时最快的排序?直接插入排序
  5. 海量数据外部排序首选?归并排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值