AL:排序基本概念/七大排序算法详解

本文介绍了排序算法的稳定性、内外部排序概念,重点阐述七大排序算法,包括插入排序(直接插入法、希尔排序)、选择排序(单向/双向直接选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序(递归与非递归算法),并对各排序时间复杂度进行了对比分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导图

在这里插入图片描述

稳定排序

  • 在待排的记录序列中,存在多个具有相同关键字的记录,如果排序之后,这些记录的相对次序保持不变,就称该排序算法是稳定的,反之不具有稳定性。

内部/外部排序

  • 内部排序:数据元素全部放在内存中的排序(数据规模较小)
  • 外部排序:数据元素太多无法放入内存中,需要借助外存进行排序;

七大排序算法

插入排序(大概有序/规模较小)
  • 基本思想:把待排序的记录按其关键码值得代销逐个插入到一个已经排好的有序序列中,直到所有记录插完为止,得到一个新的有序序列;
1.直接插入法
void InsertSort(int arr[],int size){//降序:时间O(n^2);空间O(1);稳定
    for(int i =1;i<size;i++){
        int k = arr[i];//因为后面的操作会覆盖掉arr[i],所以先存一下;
        int j = 0;
        for(j = i-1;j>=0;j--){//往前判断,小于它就跳出往后插入,如果大于就往后搬一格;
            if(arr[j]<=k){
               break;
            }else{
               arr[j+1] = arr[j];
            }
        }
        arr[j+1] = k;
    }
}
//使用二分查找进行优化;
void InsertSort_Binnary(int* arr , int size) {
        for ( int i = 1; i < size; i++ ) {
               int k = arr [i];
               int left = 0;
               int right = i - 1;
               while ( left <= right ) {
                       int mid = (left+right )/ 2;
                       if ( arr [mid] == arr [i] ) {
                              break;
                       }
                       else if ( arr [mid] > arr [i] ) {
                              right = mid - 1;
                       }
                       else {
                              left = mid + 1;
                       }
               }
               int j = 0;
               if ( arr [left] < k ) {
                       for ( j = i-1; j >= left + 1; j-- ) {
                              arr [j + 1] = arr [j];
                       }
                       arr [left + 1] = k;
               }
               else {
                       for ( j = i-1; j >=left; j-- ) {
                              arr [j + 1] = arr [j];
                       }
                       arr [left] = k;
               }
               }
        }
2.希尔排序(缩小增量排序)
  • 是插入排序的一种优化;缩小增量法,选定一个整数,把待排文件中所有记录分成组,间隔整数距离的记录在通过一个组内,并对每一个组的记录进行排序(预排序),然后重复,直到该整数递减为1,此时数组已经接近有序了,所有的记录在同一组内排好序;O(N1.3-N2);不稳定;
void InsertSortGap(int arr[],int size,int gap){//预排序过程
    for(int i =gap;i<size;i++){
        int k = arr[i];
        int j = 0;
        for(j = i-gap;j>=0;j-=gap){
            if(arr[j]<=k){
               break;
            }else{
               arr[j+gap] = arr[j];
            }
        }
        arr[j+gap] = k;
    }
}
void ShellSort(int arr[],int size){//降序
    int gap = size;
    while(1){
       gap = gap/3+1;//gap比较大的数->小->1停止;
       InsertSortGap(arr,size,gap);
       if(gap == 1){
         break;
       }
    }
}
选择排序
3.单向/双向直接选择排序(使用顺序遍历查找)
void swap(int*a,int*b){
     int t = *a;
     *a = *b;
     *b = t;
}
void SelectSort(int* arr,int size){//降序,每次选最小的数;O(n^2)O(1);不稳定
   for(int i = 0;i<size;i++){
       int min = i;//假设最开始的数是最小的;
       for(int j = i+1;j<size;j++){
           if(arr[j]<arr[min]){
               min = j;
           }
       }
       //交换最小的数到合适的位置[i];
       swap(arr+min,arr+i);
   }
}
//双向选择;
//优化版:一次遍历中既选最大又选最小;
void SelectSortPlus(int* arr,int size){//降序,每次选最小的数;O(n^2)O(1);不稳定
   int minSpace = 0;
   int maxSpace = size-1;
   while(minSpace<maxSpace){
        int min = minSpace;
        int max = minSpace;
        for(int j = minSpace+1;j<=maxSpace;j++){
             if(arr[j]<arr[min]){
                  min = j;
             }
             if (arr[j]>arr[max]){
                  max = j;
             }
        }
        swap(arr+min,arr+minSpace);
        if(minSpace==max){
            max = min ;//此时max已经被上一步交换走了;
        }
        swap(arr+max,arr+maxSpace);
        minSpace++;
        maxSpace--;
   }
}
4.堆排序(使用二叉堆)
  • O(n*logN)(向下调整的时间复杂度是O(logN));
void Swap(int* a,int *b){
      int t = *a;
      *a =*b;
      *b =t;
}
void AdjustDown(int arr[],int size,int root){
     while(1){
        int left = 2*root+1;
     if(left>=Size){
        return;
     }
    int right = 2*root+2;
    int max = left;
    if(rightt<Size&&array[right]>array[left]){
         max = right;
    }
    if(array[root]>array[max]){
        return;
    }
    Swap(arr+root,arr+max);
    root = max;
    }
}
void CreatBigHeap(int arry[],int size){//从最后一个非叶子节点点开始调整
      for(int i =( (size-1)-1)/2;i>=0;i--){
         AdjustDown(arry,size,i);
      }
}
void HeapSort(int arr[],int size){
      CreatBigHeap(arr,size);
      for(int i =0;i<size-1;i++){//将最后一个元素和堆顶交换,然后堆规模减1,堆向下调整
          int t = arr[0];
          arr[0] = arr[size-1-i];
          arr[size-1-i] = t;
          AdjustDown(arr,size-1-i,0);//注意此处第二个参数是堆的原始规模:size,第一次减1,再每次减i,我曾经将这里看成原始规模是size-1(数组下标)写成了size-2-i;
      }
}
交换排序
5.冒泡排序
  • O(n^2)
void BubbleSort(int *arr,int size){
   for(int i = 0;i<size-1,i++){
       int check = 1;
       for(int j = 0;j<size-1-i;j++){
           if(arr[j]>arr[j+1]){
              swap(arr+j,arr+j+1);
              check = 0;
           }
       }
       if(check==1){
          return;
       }
   }
}
6.快速排序(hoare/挖坑/前后指针法)
  • O(n*logN)-O(n^2)因为采用栈递归调用时时间复杂度是(O(n)-O(logN))Parition是O(n);
  • 是不稳定的,Parition过程会改变相对顺序;
  • 再要排序的区间内选择一个基准值
    • 具体方法
      • 选择区间最后边的数arr[right]
      • 遍历整个区间,做一些小的数据交换,使得:比基准值小的数放到基准值左侧,比基准值大的数放到基准值右侧;
      • 分治算法(把一个问题变成两个同样的小问题)变成了类似二叉树的结构,进行前序遍历;
    • 注意:只使用最右侧或者最左侧选为基准值,很容易让快排退化为最差情况(数列有序/逆序)
    • 解决方法
      • 三数取中:mid= (left+right)/2 ,选择arr[left],arr[mid],arr[right]中大小是中间的一个数作为基准值;选出基准值后,还是将基准值交换到最右侧再进行Parition;
      • 随机法:从left到right中随机选一个下标;(选取随机数会导致效率下降)
void Swap(int* a,int *b){
      int t = *a;
      *a =*b;
      *b =t;
}

int Parition_hoare(int *arr,int left,int right){//hoare法
    //基在右左先走
    int start = left;
    int end = right;//基准值是array[right]
    while(start<end){
       //先走左边;
       while(start<end&&arr[start]<=arr[right]){//1,1,1,1,1不加等于死循环
            start++;
       }
       //左边卡住了,走右边
       while(start<end && arr[end]>=arr[right]){
            end--;
       }
       Swap(arr+start,arr+end);
    }//区间被分成小,大,基准;
    Swap(arr+start,arr+right);//此时start已经到达了大于小于的中间位置;
    return start; //返回当前基准值所在位置;
}

int Parition_digit(int *arr,int left,int right){//挖坑法
    //坑一开始在最右所以左先走
    int start = left;
    int end = right;
    int check = arr[right];//基准值是array[right]
    while(start<end){
        while(start<end&&arr[right]<=check){
             start++;
        }
        //右侧坑
        arr[end]  = arr[start];
        while(start<end&&arr[end]>=check){
             end--;
        }
     arr[start] = arr[end];
    }
    arr[start] = check;//最后start与end指向同一个地方
    return start; //返回当前基准值所在位置;
}

int Parition_FB(int *arr,int left,int right){//前后指针法
   int small = left;
   for(int i =small;i<right;i++){//等于是不影响分组的;
       if(arr[i]<arr[right]){
          Swap(arr+small,arr+i);
          small++;
       }
   }
   Swap(arr+small,arr+right);
   return small;
}
//三数取中,返回值在中间的下标;
int PickMid(int*arr,int left,int right){
      int mid = left+(right-left)/2;
      if(arr[left]>arr[right]&&arr[left]>arr[mid]){
             return arr[mid]>arr[right]?mid:right;
      }
      else if(arr[mid]>arr[right]){
             return arr[right]>arr[left]?right:left;
      }
      else{
             return arr[mid]>arr[left]?mid:left;
      }
}

void __QuickSort(int* arr,int left,int right){//O(n*logN)
     //终止条件 size == 0|| size ==1
     // left ==right; 区间内还剩一个数
     //left>right 区间内没有数;
     if(left>=right){
         return ;
     }
     int (*slect[])(int* , int , int) = { Parition_hoare , Parition_digit , Parition_FB };
     int check_index = PickMid(arr,left,right);
     Swap(arr+check_index,arr+right);//交换到最右去;
     int div;//比基准值小的放基准值左边,大的放右边,基准值所在的下标
     div =slect[0](arr ,  left ,  right);
     __QuickSort(arr,left,div-1);
     __QuickSort(arr,div+1,right);
}
void QSort(int* arr,int size){
     __QuickSort(arr,0,size-1);
}
7.归并排序
递归算法
  • O(n*logN),稳定,可以进行外部排序//缩小数据规模开始归并;
  • 先把无序数组看为两个数组,假设这两个小数组已经有序
  • 可以使用合并两个有序数组的方式,把两个有序数组合并成一个大有序数组
  • 但假设不成立,所以大数组排序变成了两个小数组排序问题,采用分治算法知道假设成里(size0||size1);
void Merge(int *arr, int left, int mid,int right,int *extra){
      int size = right-left;
      int left_index = left;
      int right_index = mid;
      int extra_index = 0;
      while(left_index<mid&&right_index<right){
          if(arr[left_index]<=arr[right_index]){
              extra[extra_index] = arr[left_index];
              left_index++;
          }else{
             extra[extra_index] = arr[right_index];
             right_index++;
          }
          extra_index++;
      }
      while(left_index<mid){
          extra[extra_index++] = arr[left_index++];
      }
      while(right_index<right){
          extra[extra_index++] = arr[right_index++];
      }
      for(int i = 0;i<size;i++){
          arr[left+i] = extra[i];//这里要放回原数据段,原数据段是从left处开始的;
      }
}
void __MergeSort(int arr[],int left,int right,int *extra){
      if(right==left+1){//还剩一个数必有序;
          return;
      }
      if(left>=right){//区间内没有数;
          return;
      }
      int mid = left+(right - left)/2;//排序两个小区间;
      __MergeSort(arr,left,mid,extra);
      __MergeSort(arr,mid,right,extra);
      //左右两个小区间已经有序,开始合并;
      Merge(arr,left,mid,right,extra);
}
//[left,mid)[mid,right)
void MergeSort(int arr[],int size){
        int *extra = (int *)malloc(sizeof(int)*size);
       __MergeSort(arr,0,size,extra);
       free(extra);
}
非递归算法
  • 1vs1合并;2vs2合并;4vs4合并…直到合并为原始数组;
void Merge(int *arr, int left, int mid,int right,int *extra){
      int size = right-left;
      int left_index = left;
      int right_index = mid;
      int extra_index = 0;
      while(left_index<mid&&right_index<right){
          if(arr[left_index]<=arr[right_index]){
              extra[extra_index] = arr[left_index];
              left_index++;
          }else{
             extra[extra_index] = arr[right_index];
             right_index++;
          }
          extra_index++;
      }
      while(left_index<mid){
          extra[extra_index++] = arr[left_index++];
      }
      while(right_index<right){
          extra[extra_index++] = arr[right_index++];
      }
      for(int i = 0;i<size;i++){
          arr[left+i] = extra[i];//这里要放回原数据段,原数据段是从left处开始的;
      }
}

void MergeSort_Nor(int arr[],int size){
     int *extra = (int *)malloc(sizeof(int)*size);
     for(int i = 1;i<size;i*=2){//进行log2^size次,每次有i元素一组
          for(int j = 0;j<size;j+=2*i){
              int left = j;
              int mid = left+i;
              if (mid>=size){
                 break;
              }
              int right = mid+i;//[left,right)
              if (right>size){
                 right = size;
              }
              Merge(arr,left,mid,right,extra);
          }
     }
     free (extra);
}

各排序时间复杂度对比分析:

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
简单选择排序O(n^2)O(n^2)O(n^2)O(1)稳定
直接插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(nlogN)~O(n^2)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(nlogN)O(nlogN)O(nlogN)O(1)不稳定
归并排序O(nlogN)O(nlogN)O(nlogN)O(n)稳定
快速排序O(nlogN)O(nlogN)O(n^2)O(logN)~O(n)不稳定
  • 简单选择排序,堆排序,归并排序都为数据不敏感类算法,即最坏与最好的时间复杂度相同;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值