排序算法

排序算法

冒泡排序

  • 冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复地遍历数组,比较相邻元素并交换位置,将较大的元素逐步“冒泡”到数组的末尾。

  • 基本原理

    • 比较相邻元素
      • 从数组的第一个元素开始,依次比较相邻的两个元素。
      • 如果前一个元素大于后一个元素,则交换它们的位置。
    • 重复遍历
      • 每一轮遍历会将当前未排序部分的最大元素“冒泡”到正确的位置。
      • 重复上述过程,直到整个数组有序。
  • 算法步骤

    • 外层循环
      • 控制遍历的轮数,每轮将一个最大元素放到正确的位置。
      • 需要遍历 n-1 轮(n 是数组长度)。
    • 内层循环
      • 在每一轮中,比较相邻元素并交换位置。
      • 每轮结束后,未排序部分的最大元素会被放到末尾。
    • 优化
      • 如果在某一轮中没有发生交换,说明数组已经有序,可以提前结束排序。
  • 时间复杂度

    • 最优情况
      • 数组已经有序,时间复杂度为 $
        O\left( n \right)
        $(通过优化实现)。
    • 最坏情况
      • 数组完全逆序,时间复杂度为$
        O\left( n^2 \right)
        $
    • 平均情况
      • 时间复杂度为$
        O\left( n^2 \right)
        $
  • 空间复杂度

    • 冒泡排序是原地排序算法,空间复杂度为 O(1)。
  • 代码

    • public static void bubbleSort(int[] arr){
          boolean haveSwap;
      
          for (int i = 0; i < arr.length-1; i++) {
              haveSwap=false;
              for (int j = 1; j < arr.length-i; j++) {
                  int swap;
                  if(arr[j]<arr[j-1]){
                      // 交换相邻元素
                      swap=arr[j];
                      arr[j]=arr[j-1];
                      arr[j-1]=swap;
                      haveSwap=true;// 发生交换
                  }
              }
              // 如果本轮没有发生交换,说明数组已经有序
              if(!haveSwap){
                  break;
              }
          }
      }
      

选择排序

  • 选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是每次从未排序部分中选择最小(或最大)的元素,将其放到已排序部分的末尾。

  • 基本原理

    • 将数组分为两部分
      • 已排序部分(初始为空)。
      • 未排序部分(初始为整个数组)。
    • 选择最小元素
      • 在未排序部分中找到最小元素。
      • 将其与未排序部分的第一个元素交换。
    • 重复过程
      • 每次选择一个最小元素,放到已排序部分的末尾。
      • 直到整个数组有序。
  • 算法步骤

    • 外层循环

      • 控制已排序部分的边界,从 0n-1
    • 内层循环

      • 在未排序部分中找到最小元素的索引。
    • 交换元素

      • 将最小元素与未排序部分的第一个元素交换。
  • 时间复杂度

    • 无论最优、最坏还是平均都是$
      O\left( n^2 \right)
      $
    • 无论数组是否有序,选择排序都需要进行$
      n\left( n-1 \right) /2
      $次比较。
  • 空间复杂度

    • 选择排序是原地排序算法,空间复杂度为 O(1)。
  • 代码

    • public static void selectSort(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;
                  }
              }
              // 将最小元素与未排序部分的第一个元素交换
              if(i!=minIndex){
                  int swap=arr[i];
                  arr[i]=arr[minIndex];
                  arr[minIndex]=swap;
              }
      
          }
      
      }
      

插入排序

  • 插入排序(Insertion Sort)是一种简单直观的排序算法。它的基本思想是将数组分为已排序部分和未排序部分,然后逐个将未排序部分的元素插入到已排序部分的正确位置。

  • 基本原理

    • 将数组分为两部分
      • 已排序部分(初始为第一个元素)。
      • 未排序部分(初始为第二个元素到最后一个元素)。
    • 逐个插入
      • 从未排序部分中取出一个元素,将其插入到已排序部分的正确位置。
    • 重复过程
      • 直到未排序部分为空,数组完全有序。
  • 算法步骤

    • 外层循环
      • 遍历未排序部分,从第二个元素开始。
    • 内层循环
      • 将当前元素与已排序部分的元素从后向前比较,找到合适的插入位置。
    • 插入元素
      • 将当前元素插入到正确位置,已排序部分扩大。
  • 时间复杂度

    • 最优情况:数组已经有序,时间复杂度为O(n)
    • 最坏和平均都是$
      O\left( n^2 \right)
      $
  • 空间复杂度

    • 插入排序是原地排序算法,空间复杂度为 O(1)。
  • 代码

    • public static void insertSort(int[] arr) {
      
          for (int i = 1; i < arr.length; i++) {
              int key = arr[i];// 当前需要插入的元素
              int j = i - 1;
              // 将比 key 大的元素向后移动
              while (j >= 0 && key < arr[j]) {
                  arr[j + 1] = arr[j];
                  j--;
              }
              // 插入 key 到正确位置
              arr[j + 1] = key;
          }
      }
      

希尔排序

  • 希尔排序(Shell Sort)是插入排序的一种高效改进版本,也称为缩小增量排序
  • 它通过将数组分成若干子序列,对每个子序列进行插入排序,逐步缩小子序列的间隔,最终完成整个数组的排序。希尔排序的时间复杂度优于直接插入排序,适合中等规模数据排序。

基本原理:

  • 分组插入排序
    • 将数组按一定的间隔(增量)分成若干子序列。
    • 对每个子序列进行插入排序。
  • 逐步缩小间隔
    • 重复上述过程,逐步缩小间隔,直到间隔为 1。
  • 最终排序
    • 当间隔为 1 时,整个数组作为一个子序列进行插入排序,完成排序。

算法步骤:

  • 选择增量序列
    • 常用的增量序列有:希尔增量(gap = n/2, n/4, ..., 1)、Hibbard 增量等。
  • 分组插入排序
    • 对每个增量 gap,将数组分成 gap 个子序列,对每个子序列进行插入排序。
  • 缩小增量
    • 重复上述过程,直到 gap 为 1。

时间复杂度

  • 最优情况
    • 数组已经有序,时间复杂度为$
      O\left( n\log n \right)
      $
    • 最坏情况
      • 数组完全逆序,时间复杂度为$
        O\left( n^2 \right)
        $
    • 平均情况
      • 时间复杂度取决于增量序列的选择

空间复杂度

  • 希尔排序是原地排序算法,空间复杂度为 O(1)

代码

  • public static void shellSort(int[] arr){
    
        int n=arr.length;
    
        for (int gap = n/2; gap >=1 ; gap/=2) {
            //i从gap的位置开始
            for (int i = gap; i <n ; i++) {
                int key=arr[i];
                int j=i-gap;
                while (j>=0 && arr[j]>key){
                    arr[j+gap]=arr[j];
                    j-=gap;
                }
                arr[j+gap]=key;
            }
        }
    }
    

归并排序

  • 归并排序(Merge Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它的核心思想是将数组分成两个子数组,分别排序后再合并。

  • 基本原理

    • 分治法
      • 将数组递归地分成两个子数组,直到每个子数组只有一个元素。
    • 合并
      • 将两个有序的子数组合并成一个有序数组。
  • 算法步骤

    • 分解
      • 将数组从中间分成两个子数组。
    • 递归排序
      • 对左半部分和右半部分分别递归调用归并排序。
    • 合并
      • 将两个有序的子数组合并成一个有序数组。
  • 时间复杂度

    • 分解
      • 每次将数组分成两半,时间复杂度为$
        O\left( \log n \right)
        $
    • 合并
      • 每次合并需要遍历所有元素,时间复杂度为O(n)
    • 总时间复杂度:$
      O\left( n\log n \right)
      $
  • 空间复杂度

    • 归并排序需要额外的空间存储临时数组,空间复杂度为 O(n)。
  • 代码

    • public static void mergeSort(int[] arr, int beginIndex, int endIndex) {
          if (beginIndex < endIndex) {
              int mid = (beginIndex + endIndex) / 2;
              mergeSort(arr, beginIndex, mid);
              mergeSort(arr, mid + 1, endIndex);
              merge(arr, beginIndex, mid, endIndex);
          }
      
      
      }
      
      public static void merge(int[] arr, int beginIndex, int mid, int endIndex) {
          int[] swap = new int[endIndex - beginIndex + 1];
          int swapIndex = 0;
          int leftIndex = beginIndex;
          int rightIndex = mid + 1;
      
          // 比较左右两部分的元素,将较小的元素放入临时数组
          while (leftIndex <= mid && rightIndex <= endIndex) {
              if (arr[leftIndex] <= arr[rightIndex]) {
                  swap[swapIndex] = arr[leftIndex];
                  leftIndex++;
              } else {
                  swap[swapIndex] = arr[rightIndex];
                  rightIndex++;
              }
              swapIndex++;
          }
      
          // 将左半部分剩余的元素拷贝到临时数组
          while (leftIndex <= mid) {
              swap[swapIndex] = arr[leftIndex];
              leftIndex++;
              swapIndex++;
          }
      
          // 将右半部分剩余的元素拷贝到临时数组
          while (rightIndex <= endIndex) {
              swap[swapIndex] = arr[rightIndex];
              rightIndex++;
              swapIndex++;
          }
      
          if (swap.length >= 0) System.arraycopy(swap, 0, arr, beginIndex, swap.length);
      
      
      }
      

快速排序

  • 快速排序(Quick Sort)是一种基于分治法(Divide and Conquer)的高效排序算法。它的核心思想是通过选择一个基准元素(Pivot),将数组分为两部分:一部分小于基准元素,另一部分大于基准元素,然后递归地对这两部分进行排序。

  • 基本原理

    • 选择基准元素
      • 从数组中选择一个元素作为基准(Pivot)。
    • 将数组分为两部分:
      • 左部分:所有元素小于等于基准元素。
      • 右部分:所有元素大于基准元素。
    • 递归排序
      • 对左部分和右部分分别递归调用快速排序。
  • 算法步骤

    • 选择基准元素
      • 通常选择数组的第一个元素、最后一个元素或中间元素作为基准。
    • 分区
      • 使用双指针法(如 ij)将数组分为两部分。
    • 递归排序
      • 对左部分和右部分分别递归调用快速排序。
  • 时间复杂度

    • 最优情况
      • 每次分区都能将数组均匀分成两部分,时间复杂度为$
        O\left( n\log n \right)
        $
    • 最坏情况
      • 每次分区都极度不均匀(如数组已经有序),时间复杂度为$
        O\left( n^2 \right)
        $
    • 平均情况
      • 时间复杂度为$
        O\left( n\log n \right)
        $
  • 空间复杂度

    • 快速排序是原地排序算法,空间复杂度为$
      O\left( \log n \right)
      $(递归栈的深度)
  • 代码

    • public static void quickSort(int[] arr,int beginIndex,int endIndex){
          if(beginIndex<endIndex){
              //以开头元素为基准
              //需要 首先 从右遍向左找比基准元素小的元素
              //找到之后需要从坐向右找
              int key=arr[beginIndex];
              int leftIndex=beginIndex;
              int rightIndex=endIndex;
              while (leftIndex<rightIndex){
                  while (leftIndex<rightIndex && arr[rightIndex]>=key){
                      rightIndex--;
                  }
                      arr[leftIndex]=arr[rightIndex];
                  while (leftIndex<rightIndex && arr[leftIndex]<=key){
                      leftIndex++;
                  }
                      arr[rightIndex]=arr[leftIndex];
              }
              arr[leftIndex]=key;
              quickSort(arr,beginIndex,leftIndex-1);
              quickSort(arr,rightIndex+1,endIndex);
              
          }
      }
      

排序算法对比

排序算法 应用场景
冒泡排序 小规模数据,数据基本有序,教学和学习。
选择排序 小规模数据,交换次数有限,内存有限。
插入排序 小规模数据,数据基本有序,需要稳定排序,在线排序。
希尔排序 中等规模数据,对稳定性无要求,内存有限。
归并排序 大规模数据,需要稳定排序,外部排序,并行计算。
快速排序 大规模数据,对稳定性无要求,内存有限,实际应用广泛。
posted @ 2025-03-26 18:17  QAQ001  阅读(170)  评论(0)    收藏  举报