排序算法-java

下面是 Java 实现的快速排序,并带有详细的思路解析:


快速排序的核心思想

  1. 选择基准(Pivot):从数组中选择一个元素作为基准(常见策略:选第一个、最后一个、中间、随机选)。
  2. 分区(Partition):调整数组,使得左侧的元素都小于基准,右侧的元素都大于基准
  3. 递归排序(Recursion):对左右子数组分别递归执行快速排序。

Java 代码

import java.util.Arrays;

public class QuickSort {
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            // 1. 分区,找到 pivot(基准)索引
            int pivotIndex = partition(arr, left, right);
            // 2. 递归排序左半部分
            quickSort(arr, left, pivotIndex - 1);
            // 3. 递归排序右半部分
            quickSort(arr, pivotIndex + 1, right);
        }
    }

    private static int partition(int[] arr, int left, int right) {
        int pivot = arr[right]; // 选择最右侧元素作为基准
        int i = left - 1; // i 代表小于 pivot 的区域的索引

        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) { // 发现比 pivot 小的元素
                i++;
                swap(arr, i, j); // 交换,把较小元素放到前面
            }
        }
        swap(arr, i + 1, right); // 最后把 pivot 放到正确的位置
        return i + 1; // 返回 pivot 的最终索引
    }

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

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        System.out.println("排序前: " + Arrays.toString(arr));
        quickSort(arr, 0, arr.length - 1);
        System.out.println("排序后: " + Arrays.toString(arr));
    }
}

代码解析

1. quickSort(arr, left, right)
  • 递归执行快排:
    • partition() 找到 pivot 的位置。
    • 对左半部分 quickSort(arr, left, pivotIndex - 1)
    • 对右半部分 quickSort(arr, pivotIndex + 1, right)
2. partition(arr, left, right)
  • 选取 arr[right] 作为基准 pivot
  • 遍历 arr[left]arr[right-1]
    • 若元素小于 pivot,就交换到左边。
  • 最后,把 pivot 放到正确的位置,并返回其索引。
3. swap(arr, i, j)
  • 交换数组中的两个元素。

测试示例

输入:

int[] arr = {10, 7, 8, 9, 1, 5};

输出:

排序前: [10, 7, 8, 9, 1, 5]
排序后: [1, 5, 7, 8, 9, 10]

时间复杂度分析

场景时间复杂度
最优情况(均匀分割)O(n log n)
最坏情况(已排序或全部相等)O(n²)
平均情况O(n log n)

优化点:

  1. 随机化基准(Randomized Pivot):避免最坏情况。
  2. 三数取中(Median of Three):选取 (arr[left], arr[mid], arr[right]) 的中值作为 pivot,减少偏斜。
  3. 尾递归优化:减少栈调用,避免爆栈。

总结

  • 快排核心是“分区 + 递归”
  • 避免最坏情况可以优化基准选择
  • 通常比归并排序快,因为不需要额外空间

归并排序(Merge Sort)Java 实现

归并排序是一种 **分治(Divide and Conquer)**算法,它的核心思想是:

  1. 分割(Divide):把数组不断对半拆分,直到每个子数组只剩一个元素。
  2. 合并(Merge):从最小的子数组开始,两两合并,并保持有序。
  3. 递归执行:对左右两部分重复这个过程,最终完成排序。

Java 代码

import java.util.Arrays;

public class MergeSort {
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = left + (right - left) / 2; // 计算中间点
            mergeSort(arr, left, mid);   // 递归排序左半部分
            mergeSort(arr, mid + 1, right); // 递归排序右半部分
            merge(arr, left, mid, right);   // 合并两个有序数组
        }
    }

    private static void merge(int[] arr, int left, int mid, int right) {
        int n1 = mid - left + 1; // 左半部分大小
        int n2 = right - mid;    // 右半部分大小

        // 创建临时数组
        int[] leftArr = new int[n1];
        int[] rightArr = new int[n2];

        // 复制数据到临时数组
        System.arraycopy(arr, left, leftArr, 0, n1);
        System.arraycopy(arr, mid + 1, rightArr, 0, n2);

        int i = 0, j = 0, k = left; // i:左数组索引, j:右数组索引, k:原数组索引

        // 归并排序合并过程
        while (i < n1 && j < n2) {
            if (leftArr[i] <= rightArr[j]) {
                arr[k++] = leftArr[i++];
            } else {
                arr[k++] = rightArr[j++];
            }
        }

        // 处理剩余的元素(如果有)
        while (i < n1) {
            arr[k++] = leftArr[i++];
        }
        while (j < n2) {
            arr[k++] = rightArr[j++];
        }
    }

    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};
        System.out.println("排序前: " + Arrays.toString(arr));
        mergeSort(arr, 0, arr.length - 1);
        System.out.println("排序后: " + Arrays.toString(arr));
    }
}

代码解析

1. mergeSort(arr, left, right)
  • 递归拆分 数组,直到每个子数组只剩下一个元素。
  • 递归调用自身:mergeSort(arr, left, mid)mergeSort(arr, mid + 1, right)
2. merge(arr, left, mid, right)
  • 合并两个有序数组leftArrrightArr)。
  • 双指针 遍历 leftArrrightArr,将较小的元素放入 arr[k]
  • 合并完成 后,将剩余的元素复制回 arr

测试示例

输入:

int[] arr = {12, 11, 13, 5, 6, 7};

输出:

排序前: [12, 11, 13, 5, 6, 7]
排序后: [5, 6, 7, 11, 12, 13]

时间复杂度分析

场景时间复杂度
最优情况O(n log n)
最坏情况O(n log n)
平均情况O(n log n)

归并排序 稳定(不会改变相等元素的顺序),但空间复杂度 O(n),因为需要额外的临时数组。


优化点

  1. 优化小数组处理
    • 对较小的子数组(如长度 <= 10),使用插入排序代替归并排序。
  2. 原地归并(In-Place Merge)
    • 避免额外空间使用,但实现复杂。
  3. 迭代归并(非递归)
    • for 循环按 2、4、8、16... 长度逐步合并。

总结

稳定排序,适用于大规模数据排序。
时间复杂度 O(n log n),比快速排序在最坏情况下更优。
额外空间开销 O(n),适合链表排序,不适合大数据数组。

适用于 大数据排序、链表排序、外部排序(磁盘数据),而 快速排序 在内存排序时通常更快。 🚀

堆排序(Heap Sort)Java 实现

堆排序是一种 基于堆数据结构 的排序算法,利用 二叉堆(Binary Heap) 进行排序。
它的 时间复杂度始终是 O(n log n),而且 不需要额外的存储空间(原地排序),但 不是稳定排序


堆排序的核心思路

  1. 构建大顶堆(Max Heap)
    • 让数组变成一个 最大堆(即堆顶是最大元素)。
    • 最后一个非叶子节点 开始 向下调整(Heapify)
  2. 排序(交换 + 调整堆)
    • 将堆顶元素(最大值)与最后一个元素交换,缩小堆的范围。
    • 对新的堆顶执行 向下调整(Heapify),恢复最大堆性质。
    • 重复这个过程,直到数组完全有序。

Java 代码

import java.util.Arrays;

public class HeapSort {
    public static void heapSort(int[] arr) {
        int n = arr.length;

        // 1. 构建最大堆(从最后一个非叶子节点开始调整)
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 2. 依次取出堆顶元素(最大值),放到数组末尾,并调整堆
        for (int i = n - 1; i > 0; i--) {
            swap(arr, 0, i);  // 交换堆顶和最后一个元素
            heapify(arr, i, 0); // 重新调整堆(排除最后的已排序部分)
        }
    }

    // 维护最大堆性质(向下调整)
    private static void heapify(int[] arr, int n, int i) {
        int largest = i;  // 假设当前节点 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) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    // 交换数组中的两个元素
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {4, 10, 3, 5, 1};
        System.out.println("排序前: " + Arrays.toString(arr));
        heapSort(arr);
        System.out.println("排序后: " + Arrays.toString(arr));
    }
}

代码解析

1. heapSort(arr)

  • 构建最大堆for (int i = n / 2 - 1; i >= 0; i--)
    • 从最后一个非叶子节点开始(n/2 - 1),向下调整 heapify()
  • 排序(交换 + 重新堆化)
    • 交换堆顶(最大值)与最后一个元素
    • 调用 heapify() 重新调整堆。

2. heapify(arr, n, i)

  • 递归维护 最大堆,确保 arr[i] 及其子节点满足 堆的性质
    • 找到当前节点 i、左子 2i+1、右子 2i+2 中最大值
    • i 不是最大值,交换 arr[i] 与最大值,并递归调整

测试示例

输入:

int[] arr = {4, 10, 3, 5, 1};

输出:

排序前: [4, 10, 3, 5, 1]
排序后: [1, 3, 4, 5, 10]

时间复杂度分析

操作时间复杂度
构建堆O(n)
每次取出堆顶 & 重新堆化O(log n)
总时间复杂度O(n log n)

相比于快速排序(最坏 O(n²)),堆排序的时间复杂度始终是 O(n log n),适用于大数据排序。


堆排序 vs 其他排序算法

排序算法时间复杂度空间复杂度稳定性适用场景
堆排序O(n log n)O(1)❌ 不稳定适用于大规模数据,无额外空间要求
快速排序O(n log n)(最坏 O(n²))O(log n)❌ 不稳定大多数情况下比堆排序快
归并排序O(n log n)O(n)✅ 稳定适用于链表排序,外部排序
插入排序O(n²)O(1)✅ 稳定适用于小规模数据(n ≤ 50)

优化点

  1. 减少递归调用
    • 使用 非递归版本的 heapify,避免深度递归导致的栈溢出。
  2. 采用最小堆(Min Heap)
    • 若要从小到大排序,可以使用 最小堆,这样堆顶是最小值,每次取出放入结果数组末尾。

总结

时间复杂度 O(n log n),适用于大数据排序。
空间复杂度 O(1),不需要额外存储空间。
不稳定排序,相同元素的相对位置可能会改变。
比快速排序慢(因为额外的堆调整)

如果你对数据顺序要求稳定性(如事务处理),使用归并排序;如果追求速度,优先选择快速排序;如果内存有限,堆排序是不错的选择! 🚀

当然可以!下面是 希尔排序(Shell Sort)基数排序(Radix Sort) 的详细原理解析和 Java 实现代码。


✅ 一、希尔排序(Shell Sort)

📌 排序原理(思路):

  • 希尔排序是对插入排序的优化。

  • 核心思想是将数组按某个“间隔 gap”进行分组,对每组进行插入排序。

  • 每轮减少 gap,最终 gap 为 1 时进行标准插入排序,此时数据已经基本有序,所以效率较高。

  • 常用 gap 序列如:gap = gap / 2Knuth序列 等。

✅ Java 实现代码:

public class ShellSort {
    public static void shellSort(int[] arr) {
        int n = arr.length;
        // 从数组长度的一半开始,不断缩小 gap,直到 gap = 1
        for (int gap = n / 2; gap > 0; gap /= 2) {
            // 对每个间隔进行插入排序
            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;
            }
        }
    }

    // 示例测试
    public static void main(String[] args) {
        int[] arr = {9, 8, 3, 7, 5, 6, 4, 1};
        System.out.println("排序前: " + java.util.Arrays.toString(arr));
        shellSort(arr);
        System.out.println("排序后: " + java.util.Arrays.toString(arr));
    }
}

⏱️ 时间复杂度(取决于 gap 序列):

最好最坏平均空间稳定性
O(n log n)O(n²)O(n^1.3)O(1)❌ 不稳定

✅ 二、基数排序(Radix Sort)

📌 排序原理(思路):

  • 基数排序是一种非比较型排序,基于“分配与收集”思想。

  • 按位处理,从最低位到最高位(LSD法),逐位使用稳定的排序方法(如计数排序)排序。

  • 避免了元素之间的比较,适用于非负整数或字符串排序

示例:对 [170, 45, 75, 90, 802, 24, 2, 66]
先按个位排 → 十位 → 百位。


✅ Java 实现代码:

public class RadixSort {
    // 主函数
    public static void radixSort(int[] arr) {
        int max = getMax(arr); // 获取最大数,确定位数

        // 从个位开始排序(exp 表示位权:1, 10, 100...)
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }

    // 按当前位(exp)进行计数排序
    private static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n];
        int[] count = new int[10]; // 0~9 共10个数字

        // 1. 统计当前位数字出现次数
        for (int i = 0; i < n; i++) {
            int digit = (arr[i] / exp) % 10;
            count[digit]++;
        }

        // 2. 累加 count 数组,确定索引位置
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1];
        }

        // 3. 从后往前遍历,构建 output(保持稳定性)
        for (int i = n - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % 10;
            output[--count[digit]] = arr[i];
        }

        // 4. 拷贝回原数组
        System.arraycopy(output, 0, arr, 0, n);
    }

    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int val : arr) {
            if (val > max) max = val;
        }
        return max;
    }

    // 示例测试
    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
        System.out.println("排序前: " + java.util.Arrays.toString(arr));
        radixSort(arr);
        System.out.println("排序后: " + java.util.Arrays.toString(arr));
    }
}

⏱️ 时间复杂度:

最好最坏平均空间稳定性
O(nk)O(nk)O(nk)O(n + k)✅ 稳定

其中 k 是数字位数,n 是元素数量。


如果你还需要基数排序处理负数或字符串的版本,也可以告诉我,我可以一起扩展。需要我整理成 Markdown 或 PDF 文件也没问题~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值