排序算法专题

文章目录

一、排序的基本概念

算法的稳定性
  • 关键字相同的元素经过排序后相对顺序是否会改变
内部排序与外部排序
  • 内部排序:数据都在内存中----关注时间、空间复杂度、稳定性
  • 外部排序:数据太多,无法全部放入内存----还需关注磁盘读取次数

二、插入排序

直接插入排序
  • 每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中
  • 平均时间复杂度:O(n^2)
  • 算法稳定性:稳定
c 复制代码
void InsertSort(int A[],int n){
    int i,j,temp;
    for(i=1;i<n;i++){
        if(A[i]<A[i-1]){
            temp=A[i];
            for(j=i-1;j>=0&&A[j]>temp;--j){
                A[j+1]=A[j]; // 从后往前检查,比当前元素大的右移
            }
            A[j+1]=temp;
        }
    }
}
希尔排序
  • 先追求表中元素部分有序,再逐渐逼近全局有序
  • 仅适用于顺序表
  • 算法过程:
    • 相隔增量d的元素组成同一个子表,对各个子表分别进行直接插入排序,缩小增量d,直到d=1为止
  • 算法稳定性:不稳定
    • 相同关键字划分到不同子表,可能改变相对次序
c 复制代码
void ShellSort(int A[],int n){
    int i,j,temp,d;
    for(d=n/2;d>=1;d=d/2){
        for(i=d;i<n;i++){
            if(A[i]<A[i-d]){
                temp=A[i];
                for(j=i-d;j>=0&&A[j]>temp;j-=d){
                    A[j+d]=A[j]; // 检查前面比当前元素大的右移
                }
                A[j+d]=temp;
            }
        }
    }
}

三、交换排序

冒泡排序
  • 从后往前(或从前往后)两两比较相邻元素的值,若为逆序则交换
  • 平均时间复杂度:O(n^2)
  • 算法稳定性:稳定
c 复制代码
void BubbleSort(int A[],int n){
    int i,j,temp,flag;
    for(i=0;i<n-1;i++){
        flag=false;
        for(j=n-1;j>i;j--){
            if(A[j]<A[j-1]){ //相等不交换保持稳定
                temp=A[j];
                A[j]=A[j-1];
                A[j-1]=temp;
                flag=true;
            }
        }
        if(flag==false)
            return; //本趟遍历后没有发生交换,说明已经有序
    }
}
快速排序
  • 用枢轴元素pivot划分待排序表
  • 算法过程
    • 两个指针lowhigh向中间移动,将小于pivot的元素放到左边,大于pivot的元素放到右边,pivot元素放到最终位置上,再对左右递归
  • 快速排序是所有内部排序算法中平均性能最优的排序算法
  • 空间复杂度:
    • 最好O(log_2n)
    • 最坏O(n)
    • 平均O(log_2n)
  • 平均时间复杂度:O(nlog_2n)
  • 稳定性:不稳定
c 复制代码
int Partition(int A[],int low,int high){
    int pivot=A[low];
    while(low<high){
        while(low<high&&A[high]>=pivot){
            --high;
        }
        A[low]=A[high]; // 比枢轴小的元素移动到左边
        while(low<high&&A[low]<=pivot){
            ++low;
        }
        A[high]=A[low]; // 比枢轴大的元素移动到右边
    }
    A[low]=pivot;
    return low;
}

void Quicksort(int A[],int low,int high){
    if(low<high){ //递归跳出条件
        int pivotpos=Partition(A,low,high);
        Quicksort(A,low,pivotpos-1);
        Quicksort(A,pivotpos+1,high);
    }

}

四、选择排序

简单选择排序
  • 每一趟在待排元素中选取关键字最小(或最大)的元素加入有序子序列
  • 平均时间复杂度:O(n^2)
  • 稳定性:不稳定
c 复制代码
void Selectsort(int A[],int n){
    int i,j,temp,min;
    for(i=0;i<n-1;i++){
        min=i;
        for(j=i+1;j<n;j++){
            if(A[j]<A[min]){
                min=j; //得到最小关键字的索引
            }
        }
        if(min!=i){
            temp=A[min];
            A[min]=A[i];
            A[i]=temp;
        }
    }
}
堆排序
  • 将待排序列整理成大根堆(或小根堆)后进行选择排序
  • 大根堆:根>=左、右
  • 建立大根堆:
    • 在顺序存储的完全二叉树中,非终端节点编号i<=⌊n/2⌋
    • 由后向前检查所有非终端节点,若不满足根>=左、右,将当前节点与更大的一个孩子互换
    • 若元素互换破坏了下一级的堆,则采用相同方法继续向下调整
    • 建堆的时间复杂度:O(n)
  • 输出堆顶元素:最后一个元素替换堆顶
  • 算法过程
    • 首先建立大根堆,不断输出堆顶元素,并将剩余元素序列再次调整为大根堆
  • 堆的插入删除
    • 插入:插入到堆底后"上升"
    • 删除:用堆底元素替换后"下坠"
  • 平均时间复杂度:O(nlog_2n)
  • 稳定性:不稳定
c 复制代码
//建立大根堆
void BuildMaxHeap(int A[],int len){
    int i;
    for(i=len/2;i>0;i--){ //从后往前调整所有非终端节点
        HeadAdjust(A,i,len);
    }
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int k,int len){
    int i;
    A[0]=A[k]; //A[0]暂存防止覆盖
    for(i=2*k;i<=len;i*=2){
        if(i<len&&A[i]<A[i+1]){
            i++; //获取左右孩子中更大的
        }
        if(A[0]>A[i])
            break;
        else{
            A[k]=A[i]; //A[i]调整到双亲结点上
            k=i; //向下继续筛选
        }
    }
    A[k]=A[0]; //注意此时k值已经改变,为该节点最终位置
}

// 堆排序
void HeapSort(int A[],int len){
    BuildMaxHeap(A,len);
    int temp;
    for(int i=len;i>1;i--){
        temp=A[i];
        A[i]=A[1];
        A[1]=temp; //堆顶元素和堆底元素互换
        HeadAdjust(A,1,i-1); //把剩余待排元素整理为大根堆
    }
}

五、归并排序

二路归并排序
  • 将相邻的两个有序表合并成一个
归并排序
  • 将n个待排元素分成两个长度相等的子序列,分别对两个子序列递归调用归并方法,再将两有序子序列二路归并排序
  • 空间复杂度:O(n)
  • 平均时间复杂度:O(nlog_2n)
  • 稳定性:稳定
c 复制代码
int *B=(int *)malloc((n+1)*sizeof(int)); // 辅助数组B

void Merge(int A[],int low,int mid,int high){// mid分割两个待归并序列
    for(int k=low;k<=high;k++){
        B[k]=A[k]; //复制到辅助数组中
    }
    int i,j,k;
    for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
        if(B[i]<=B[j]){
            A[k]=B[i++];
        }else{
            A[k]=B[j++];
        }
    }
    while(i<=mid)
        A[k++]=B[i++];
    while(j<=high){
        A[k++]=B[j++];
    }
}

void MergeSort(int A[],int low,int high){
    if(low<high){
        int mid=(low+high)/2;
        MergeSort(A,low,mid);
        MergeSort(A,mid+1,high);
        Merge(A,low,mid,high); //归并
    }
}

六、基数排序

多关键字排序
  • 按照关键字各位大小排序
  • n:待排元素个数
  • d:单个元素分量的个数
  • rd:基数,每个分量的取值范围
  • 单逻辑关键字:文件中任意一关键字k均由d个分量构成,且每个分量都是单独的关键字
  • 排序方法:
    • 最高位优先法MSD--逐层分割成若干子序列
    • 最低位优先法LSD--不必分成若干子实现排序序列,不通过关键字比较,通过"分配"+"收集"实现排序
链式基数排序
  • 算法过程:
    • 将关键字拆分为d位(或组)
    • 按照各个关键字位权重递增的次序(LSD方法),做d趟分配O(n)和收集O(rd)
  • 空间复杂度:O(rd)
  • 时间复杂度:O(d(n+rd))
  • 稳定性:稳定

七、内部排序算法的比较

  • 各算法性质比较表
排序算法 最好时间复杂度 平均时间复杂度 最坏时间复杂度 空间复杂度(平均) 是否稳定
直接(折半)插入排序 O(n) O(n2) O(n2) O(1) 稳定
希尔排序 - - - O(1) 不稳定
冒泡排序 O(n) O(n2) O(n2) O(1) 稳定
快速排序 O(nlog2n) O(nlog2n) O(n2) O(log2n) 不稳定
简单选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
堆排序 O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不稳定
归并排序 O(nlog2n) O(nlog2n) O(nlog2n) O(n) 稳定
基数排序 O(d(n+rd)) O(d(n+rd)) O(d(n+rd)) O(rd) 稳定
  • 规律总结

    • 每次排序确定一个最终位置:插入排序,冒泡排序,选择排序,快速排序
    • 算法复杂度与初始状态无关:选择排序、堆排序、归并排序、基数排序
    • 比较次数与初始状态无关:选择排序、基数排序
    • 移动次数与初始状态无关:归并排序、基数排序
    • 排序趟数与初始状态有关:快速排序、冒泡排序
  • 根据关键字分布情况选择排序方法

    • n较小,基本有序:直接插入排序
    • n较小,数据项较多,所占存储空间较大:简单选择排序
相关推荐
天天开心(∩_∩)16 分钟前
代码随想录算法训练营第三十九天
算法
weisian1511 小时前
力扣经典算法篇-41-旋转图像(辅助数组法,原地旋转法)
算法·leetcode·职场和发展
朝朝又沐沐2 小时前
算法竞赛阶段二-数据结构(40)数据结构栈的STL
开发语言·数据结构·c++·算法
2501_927773072 小时前
数据结构——单向链表
数据结构·算法
程序猫.2 小时前
C语言 二分查找:高效搜索有序数组
c语言·算法
weisian1513 小时前
力扣经典算法篇-38-组合(回溯算法)
算法·leetcode·职场和发展
努力写代码的熊大3 小时前
八大排序算法
java·算法·排序算法
xiaobaibai1534 小时前
智慧交通中目标检测 mAP↑28%:陌讯多模态融合算法实战解析
人工智能·算法·目标检测·计算机视觉·目标跟踪·视觉检测
战争热诚4 小时前
基于transformer的目标检测——匈牙利匹配算法
算法·目标检测·transformer