常见的排序算法

一、快速排序

基本思想

选取一个基准值,将数组分成大于这个值的一部分和小于这个值的一部分,再用同样的方法处理这两部分的数据。

代码

java 复制代码
package Sort;

import java.io.*;

/**
 * 快速排序
 * 最快时间复杂度:O(nlogn)
 * 最差时间复杂度:O(n^2)
 * 不稳定
 * 想象成一个二叉树,根节点是数组的第一个元素,左右子树是数组的剩余元素。每层是n,层高是logn,所以时间复杂度是O(nlogn)。
 */
public class QuickSort {
    public static void main(String[] args) throws IOException {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("排序前的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
        quickSort(arr, 0, arr.length - 1);

        System.out.println("排序后的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }

    private static void quickSort(int[] arr, int l, int r) {
        if(l >= r) {
            return;
        }
        int q = arr[l];
        int i = l;
        int j = r;

        while (i < j) {
            //从右往左找
            while (i < j && arr[j] > q) {
                j--;
            }
            if (i < j) {
                arr[i++] = arr[j];
            }
            //从左往右找
            while (i < j && arr[i] < q) {
                i++;
            }
            if (i < j) {
                arr[j--] = arr[i];
            }
        }
        arr[i] = q;
        quickSort(arr, l, i - 1);
        quickSort(arr, i + 1, r);
    }
}

二、归并排序

基本思想

将数组分成左右两部分,对这两部分分别进行排序然后将两个排好序的数组合并。

代码

java 复制代码
package Sort;

import java.io.*;
import java.util.Arrays;

/**
 * 归并排序
 * 稳定的时间复杂度:O(nlogn)
 * 空间复杂度:O(n)
 * 稳定排序
 */
public class MergeSort {

    public static void main(String[] args) throws IOException {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("排序前的数组:");
        System.out.println(Arrays.toString(arr));

        mergeSort(arr, 0, arr.length - 1);

        System.out.println("排序后的数组:");
        System.out.println(Arrays.toString(arr));
    }

    private static void mergeSort(int[] arr, int l, int r) {
        if (l >= r) {
            return;
        }

        // 计算中间位置
        int mid = l + (r - l) / 2;

        // 递归排序左半部分
        mergeSort(arr, l, mid);
        // 递归排序右半部分
        mergeSort(arr, mid + 1, r);

        // 合并两个有序数组
        merge(arr, l, mid, r);
    }

    private static void merge(int[] arr, int l, int mid, int r) {
        // 创建临时数组
        int[] temp = new int[r - l + 1];

        int i = l;      // 左半部分的指针
        int j = mid + 1; // 右半部分的指针
        int k = 0;       // 临时数组的指针

        // 比较两个部分的元素,将较小的放入临时数组
        while (i <= mid && j <= r) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        // 将左半部分剩余的元素复制到临时数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 将右半部分剩余的元素复制到临时数组
        while (j <= r) {
            temp[k++] = arr[j++];
        }

        // 将临时数组的元素复制回原数组
        for (int m = 0; m < temp.length; m++) {
            arr[l + m] = temp[m];
        }
    }
}

三、插入排序

基本思想

选取一个元素排好序,然后下一个元素归并到这个有序数组。

代码

java 复制代码
package Sort;

/**
 * 插入排序算法实现
 * 基本思想:将未排序的元素逐个插入到已排序序列的正确位置
 * 最差时间复杂度:O(n²)
 * 最好时间复杂度: O(n),越接近有序的序列,时间复杂度越低
 * 空间复杂度:O(1)
 * 稳定排序
 */
public class InsertionSort {
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("排序前的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
        insertionSort(arr);
        System.out.println("排序后的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        // 从第二个元素开始,逐个插入到前面的已排序序列中
        for (int i = 1; i < n; i++) {
            int cur = arr[i];  // 当前要插入的元素
            int pre = i - 1;
            // 将比cur大的元素向后移动
            while(pre >= 0 && cur < arr[pre]){
                arr[pre + 1] = arr[pre];
                pre--;
            }
            // 将cur插入到正确位置
            arr[pre + 1] = cur;
        }
    }
}

四、堆排序

基本思想

最大堆的堆顶是堆中所有元素中最大的一个,利用这个特性,每次从堆顶选出最大元素交换到数组的末尾,再对剩下的元素重新建堆(只用对堆顶元素heapify),如此往复。

ps:如何建立最大堆?父节点一定大于等于两个字节点,从下往上初始化一个最大堆,先找到最后一个非叶子节点(n/2 - 1),再(n/2-1)--依次建立父节点大于子节点的最大堆。

代码

java 复制代码
package Sort;

/**
 * 堆排序算法实现
 * 基本思想:利用最大堆(或最小堆)的性质进行排序
 * 最差时间复杂度:O(n log n)
 * 最好时间复杂度:O(n log n)
 * 空间复杂度:O(1)
 * 不稳定排序
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};

        System.out.println("排序前的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();

        heapSort(arr);

        System.out.println("排序后的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }

    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--) {
            // 将当前最大值(根节点)移到数组末尾
            swap(arr, 0, i);
            // 对剩余元素重新调整堆
            heapify(arr, i, 0);
        }
    }

    private static void heapify(int[] arr, int heapSize, int rootIndex) {
        int largest = rootIndex;  // 初始化最大值为根节点
        int left = 2 * rootIndex + 1;   // 左子节点
        int right = 2 * rootIndex + 2;  // 右子节点
        
        // 如果左子节点大于根节点
        if (left < heapSize && arr[left] > arr[largest]) {
            largest = left;
        }
        
        // 如果右子节点大于当前最大值
        if (right < heapSize && arr[right] > arr[largest]) {
            largest = right;
        }
        
        // 如果最大值不是根节点,则交换并继续调整
        if (largest != rootIndex) {
            swap(arr, rootIndex, largest);
            heapify(arr, heapSize, largest);
        }
    }

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

}

五、冒泡、选择排序:只入门,项目彻底不用

基本思想

冒泡排序从左到右两两比较,得到最大的放末尾,然后对剩下的n-1个元素重复这个操作

选择排序遍历得到最大的一个元素然后放末尾,然后对剩下的n-1个元素重复这个操作


六、希尔排序:几乎彻底淘汰

基本思想

跳步分组,组内进行插入排序

如:数组:[8,2,5,1,7,3,6],长度 7

  1. 第一轮间隔 3分组:(0,3,6)、(1,4)、(2,5),每组内部插入排序
  2. 第二轮间隔 1间隔变成 1,等同于整个数组做普通插入排序,排完整体有序

七、选型口诀

  1. 常规排序选快排,最优nlogn最慢平方态
  2. 要求稳定用归并,所有场景都是nlogn
  3. 求取 TopK 堆排上,复杂度全程不波动
  4. 小数有序插排优,顺序越好越接近O(n)
  5. 冒泡选择学基础,平方复杂度不投产
  6. 希尔算法已淘汰,日常开发用不上
相关推荐
gumichef5 小时前
二叉树链式结构的实现
算法·链表·二叉树·队列
战南诚5 小时前
力扣 之 198.打家劫舍
python·算法·leetcode
AllData公司负责人5 小时前
亲测丝滑,体验跃迁|AllData通过集成开源项目StreamPark,实时流任务调度更省心!
java·大数据·数据库·人工智能·算法·实时计算·实时开发平台
计算机安禾5 小时前
【c++面向对象编程】第46篇:CRTP(奇异递归模板模式):静态多态的妙用
开发语言·c++·算法
广州灵眸科技有限公司5 小时前
瑞芯微(EASY EAI)RV1126B 音频电路
开发语言·人工智能·深度学习·算法·yolo·音视频
小的~~5 小时前
算法题:只出现一次的数字
数据结构·算法
灵智实验室5 小时前
PX4状态估计技术EKF2详解(六):EKF2 磁力计融合——从航向修正到 3D 姿态约束
算法·无人机·px 4
JieE2125 小时前
手把手带你用虚拟头节点实现单链表,搞定所有边界问题
javascript·算法
历程里程碑6 小时前
56 . 高效ET非阻塞IO服务器设计指南
java·运维·服务器·开发语言·数据结构·c++·排序算法