【数据结构】排序算法大全(快速、堆、归并、插入、折半、希尔、冒泡、计数、基数)各算法比较、解析+完整代码

news2024/12/24 2:39:39

文章目录

  • 八、排序
    • 1.插入排序
      • 1.1 直接插入排序
      • 1.2 折半插入排序
      • 1.3 希尔排序
    • 2.交换排序
      • 2.1 冒泡排序
      • 2.2 快速排序
    • 3.选择排序
      • 3.1 简单选择排序
      • 3.2 堆
        • 3.2.1 堆排序
        • 3.2.2 堆插入删除
        • *完善代码 堆
    • 4.归并、基数、计数排序
      • 4.1 归并排序
      • 4.2 基数排序
      • 4.3 计数排序
    • 5.内部排序算法的比较
    • *完整代码 排序

八、排序

1.插入排序

1.1 直接插入排序

  • 算法思想

    每次将一个待排序的记录按其关键字大小插入到前面已排序好的子序列中,直到全部记录插入完成。

    在这里插入图片描述

  • 代码

    不带哨兵:

    void InsertSort(int A[],int n){
        int i,j,temp;
        for(i=0;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;//插入
            }
        }
    }
    

    带哨兵(第一个元素存待插入的元素):

    优点:不用每轮循环都判断j>=0

    void InsertSort(int A[],int n){
        int i,j;
        for(i=2;i<=n;i++){
            if(A[i]<A[i-1]){
                A[0]=A[i]; //哨兵
                for(j=i-1;A[0]<A[j];j--) //从后往前查找待插入位置
                    A[j+1]=A[j];
                A[j+1]=A[0];
            }
        }
    }
    
  • 算法效率

    • 空间复杂度: O ( 1 ) O(1) O(1)

    • 时间复杂度:主要来自对比关键字、移动元素。

      最好情况:共n-1趟处理,每一趟只需要对比一次,则 O ( n ) O(n) O(n)

      最坏情况:原本为逆序,则 O ( n 2 ) O(n^2) O(n2)

      平均时间复杂度: O ( n 2 ) O(n^2) O(n2)

      算法稳定性:稳定

1.2 折半插入排序

  • 算法思想

    在已排好的部分设置low和high指针,取中间点mid和待排序数据比较。

    当low>high时折半查找停止,应将[low,i-1]内的元素全部右移,并将A[0]复制到low所指位置;

    当A[mid]==A[0]时,为了保证算法的稳定性,应继续在mid所指位置右边寻找插入位置。

  • 代码

    void InsertSort(int A[],int n){
        int i,j,low,high,mid;
        for(i=2;i<=n;i++){
            A[0]=A[i];
            low=1;high=i-1; //折半查找范围
            while(low<=high){
                mid=(low+high)/2;
                if(A[mid]>A[0])
                    high=mid-1; //查找左子表
                else
                    low=mid+1; //查找右子表
            for(j=i-1;j>=high+1;i--)
                A[j+1]=A[j]; //后移元素,空出插入位置
            A[high+1]=A[0]; //插入
            }
        }
    }
    
  • 算法效率

    时间复杂度: O ( n 2 ) O(n^2) O(n2)

  • 对链表进行插入排序

    • 折半插入排序无法实现
    • 直接插入排序移动元素次数变少了,但关键字比较次数依然 O ( n 2 ) O(n^2) O(n2),所以时间复杂度也是 O ( n 2 ) O(n^2) O(n2)

1.3 希尔排序

  • 思想

    先部分有序,再全局有序。

    先将待排序表分割成若干形如 L [ i , i + d , i + 2 d , . . . , i + k d ] L[i,i+d,i+2d,...,i+kd] L[i,i+d,i+2d,...,i+kd]特殊子表,对各个子表分别进行直接插入排序。缩小增量d,重复上述过程,直到d=1为止。

  • 过程

在这里插入图片描述
在这里插入图片描述

  • 代码

    void ShellSort(int A[],int n){
        int d,i,j;
        //A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
        for(d=n/2;d>=1;d=d/2){ //步长变化
            for(i=d+1;i<=n;i++){
                if(A[i]<A[i-d]){ //需将A[i]插入有序增量子表
                    A[0]=A[i];
                    for(j=i-d;j>0&&A[0]<A[j];j-=d){
                        A[j+d]=A[j]; //记录后移
                    }
                    A[j+d]=A[0]; //插入
                }
            }
        }
    }
    
  • 性能分析

    • 空间复杂度: O ( 1 ) O(1) O(1)

    • 时间复杂度:和增量序列d的选择有关。

      目前无法用数学手段确切证明时间复杂度,最坏时间复杂度 O ( n 2 ) O(n^2) O(n2),n在某个范围内,可达 O ( n 1.3 ) O(n^{1.3}) O(n1.3)

    • 不稳定

    • 不能基于链表实现。

2.交换排序

  • 基于交换的排序:根据序列中两个元素关键字的比较结果来对这两个记录在序列中的位置。

2.1 冒泡排序

  • 算法思想

    从后往前(或从前往后)两两比较相邻元素的值,若为逆序,则交换,直到序列比较完。

  • 代码

    //交换
    void swap(int &a,int &b){
        int temp=a;
        a=b;
        b=temp;
    }
    
    //冒泡排序
    void BubbleSort(int A[],int n){
        for(int i=0;i<=n-1;i++){
            bool flag=false; //表示本趟是否发生交换
            for(int j=n-1;j>i;j--){
                if(A[j-1]>A[j]){ //若为逆序
                    swap[A[j-1],A[j]];
                    flag=true;
                }
            }
            if(flag==false)return ; //本趟遍历没有发生交换,说明已经有序
        }
    }
    
  • 性能分析

    • 空间复杂度: O ( 1 ) O(1) O(1)

    • 时间复杂度:

      最好情况——有序 O ( n ) O(n) O(n)

      最坏情况——逆序 O ( n 2 ) O(n^2) O(n2)

    • 可以用于链表的排序。

2.2 快速排序

  • 算法思想

    基于分治法。

    1.在序列中任取一个元素作为基准(通常取首元素);

    2.遍历序列,将比基准元素小的放在基准元素的左边,比基准元素大的放在右边;

    3.遍历完成后,基准元素位置确定,对该基准元素的左右子表再次排序。

    在这里插入图片描述

  • 代码

    思路

    1.以序列第一个元素作为基准元素pivot;

    2.令low和high分别指向序列首尾;

    3.high不断左移直到找到比pivot小的元素,找到后交换low和high所指元素的位置;

    4.low不断右移直到找到比pivot大的元素,找到后交换low和high所指元素位置;

    5.当low==high时完成遍历,将基准元素放入low指针所指的地方,此时基准元素已经排序完成;

    6.递归排序基准元素的左右子序列。

    //用第一个元素将待排序序列划分为左右两个部分
    int Partition(int A[],int low,int high){
        int pivot=A[low]; //第一个元素作为基准
        while(low<high){
            while(low<high&&A[high]>=pivot) //high指针不断左移,直到找到比基准元素小的
                high--;
            A[low]=A[high]; //比基准小的元素左移
            while(low<high&&A[low]<=pivot) //low指针不断右移,直到找到比基准元素大的
                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 ( l o g 2 n ) O(log_2n) O(log2n),最坏空间复杂度 O ( n ) O(n) O(n)

    • 时间复杂度=n*递归层数

      最好时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),最坏时间复杂度 O ( n 2 ) O(n^2) O(n2)

      平均时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

    • 稳定性:不稳定

  • 优化

    • 若每次选中的基准元素将待排序序列划分成均匀两个部分,则递归深度最小,算法效率最高。

    • 优化思路:

      尽量选择可以把数据中分的数轴元素。

      如:1.选头中尾三个位置的元素,取中间值作为基准元素;2.随机选取一个元素。

3.选择排序

3.1 简单选择排序

  • 定义

    每一趟在待排序元素中选取关键字最小的元素加入有序子序列。

  • 代码

    //交换函数
    void swap(int &a,int &b){
        int temp=a;
        a=b;
        b=temp;
    }
    
    void SelectSort(int A[],int n){
        for(int i=0;i<n-1;i++){
            int min=i;
            for(int j=1+1;j<n;j++) //选择最小的
                if(A[j]<A[min])min=j;
            if(min!=i)swap(A[i],A[min]);
        }
    }
    
  • 性能分析

    • 空间复杂度: O ( 1 ) O(1) O(1)

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

      无论有序、逆序、乱序,一定需要n-1趟处理

      总共需要对比关键字 ( n − 1 ) + ( n − 2 ) + . . . + 1 = n ( n − 1 ) / 2 (n-1)+(n-2)+...+1=n(n-1)/2 (n1)+(n2)+...+1=n(n1)/2

      交换元素次数<n-1

    • 稳定性:不稳定。

    • 实用性:既可以顺序表,也可以链表。

3.2 堆

3.2.1 堆排序
  • 什么是堆?

    若n个关键字序列 L [ 1... n ] L[1...n] L[1...n]满足以下某条性质,则称为堆(Heap):

    1.若满足 L ( i ) > = L ( 2 i ) L(i)>=L(2i) L(i)>=L(2i) L ( i ) > = L ( 2 i + 1 ) L(i)>=L(2i+1) L(i)>=L(2i+1) ( 1 < = i < = n / 2 ) (1<=i<=n/2) (1<=i<=n/2) —— 大根堆(大顶堆)

    2.若满足 L ( i ) < = L ( 2 i ) L(i)<=L(2i) L(i)<=L(2i) L ( i ) < = L ( 2 i + 1 ) L(i)<=L(2i+1) L(i)<=L(2i+1) ( 1 < = i < = n / 2 ) (1<=i<=n/2) (1<=i<=n/2) —— 小根堆(小顶堆)

  • 如何建立大根堆

    • 思路:把所有非终端结点( l e n / 2 到 1 len/2到1 len/21的结点)都检查一遍,是否满足大根堆要求,如果不满足,则调整。
    • 检查方式:检查当前结点是否满足 根>=左右,若不满足,将当前结点与更大的一个孩子互换
  • 代码

    思路:

    1.从len/2开始遍历;

    2.检查是否满足大/小根堆的要求;

    3.若不满足,将当前结点与更加大/小的孩子换。

    //建立大根堆
    void BuildMaxHeap(int A[],int len){
        for(int i=len/2;i>0;i--) //从后往前调整所有非终端结点
            HeadAdjust(A,i,len);
    }
    
    //将以k为根的子树调整为大根堆
    void HeadAdjust(int A[],int k,int len){
        A[0]=A[k]; //A[0]暂存子树根结点
        for(int i=2*k;i<=len;i*=2){ //沿key较大的子结点向下筛选
            if(i<len&&A[i]<A[i+1])//取key较大的子结点的下标  
                i++; 
            if(A[0]>=A[i])
                break; //筛选结束
            else{
                A[k]=A[i]; //将A[i]调整到双亲结点上
                k=i; //修改k值,以便继续向下筛选
            }
        }
        A[k]=A[0]; //被筛选结点的值放入最终位置
    }
    
  • 基于大根堆进行排序

    1.每一趟将堆顶元素加入有序子序列;

    2.与待排序序列中的最后一个元素交换;

    3.并将待排序元素序列再次调整为大根堆(小元素不断下坠)。

    代码:

    //建立大根堆
    void BuildMaxHeap(int A[],int len)
        
    //将以k为根的子树调整为大根堆
    void HeadAdjust(int A[],int k,int len)
        
    //堆排序完整逻辑
    void HeapSort(int A[],int len){
        BuidMaxHeap(A,len); //初始建堆
        for(int i=len;i>1;i--){
            swap(A[i],A[1]); //堆顶和堆底元素交换
            HeadAdjust(A,1,i-1); //把剩余的待排序元素整理成堆
        }
    }
    
  • 时间复杂度分析

    • 一个结点,每下坠一层,最多只需对比关键字2次

      若树高为h,某结点在第i层,则将这个结点向下调整最多只需要下坠h-i层,关键字对比次数不超过2(h-i)

    • 建堆过程,关键字对比次数不超过4n,建堆时间复杂度 O ( n ) O(n) O(n)

    • 每下坠一层,最多只需要对比关键字两次,因此每一趟排序复杂度不超过 O ( h ) = O ( l o g 2 n ) O(h)=O(log_2n) O(h)=O(log2n)

    • 总共有n-1趟,总时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

  • 稳定性

    若左右孩子一样大,优先和左孩子换,建堆时是稳定的,但是在排序时,堆顶和堆底互换,不稳定。

    ∴不稳定

  • 空间复杂度: O ( 1 ) O(1) O(1)

3.2.2 堆插入删除
  • 在堆中插入新元素

    • 对于小根堆,新元素放到表尾(堆底),与父节点对比,若新元素比父节点更加小,则互换。

      新元素一路上升,直到无法继续上升。

    • 对于大根堆,新元素放到表尾,与父节点对比,若新元素比父节点更加大,则互换。

      新元素一路上升,直到无法继续上升。

  • 在堆中删除

    • 被删除元素用堆底(表尾)元素代替,

      根据大/小根堆要求,让该元素不断下坠,直到无法下坠为止。

  • 关键字对比次数

    每次上升只需对比1次;

    每次下降可能2次(有两小孩),可能1次(只有一小孩)。

*完善代码 堆
#include <stdio.h>
#include <math.h>

// 辅助函数,用于交换两个整数的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len) {
    A[0] = A[k]; // A[0]暂存子树根结点
    for (int i = 2 * k; i <= len; i *= 2) { // 沿key较大的子结点向下筛选
        if (i < len && A[i] < A[i + 1]) // 取key较大的子结点的下标
            i++;
        if (A[0] >= A[i])
            break; // 筛选结束
        else {
            A[k] = A[i]; // 将A[i]调整到双亲结点上
            k = i; // 修改k值,以便继续向下筛选
        }
    }
    A[k] = A[0]; // 被筛选结点的值放入最终位置
}

// 建立大根堆
void BuildMaxHeap(int A[], int len) {
    // 从最后一个非终端节点开始,依次向前调整堆
    for (int i = len / 2; i > 0; i--) {
        HeadAdjust(A, i, len);
    }
}

// 堆排序完整逻辑
void HeapSort(int A[], int len) {
    BuildMaxHeap(A, len); // 初始建堆
    for (int i = len; i > 1; i--) {
        swap(&A[i], &A[1]); // 堆顶和堆底元素交换
        HeadAdjust(A, 1, i - 1); // 把剩余的待排序元素整理成堆
    }
}

// 插入新元素到大根堆
void Insert(int A[], int *len, int value) {
    A[++(*len)] = value; // 在数组末尾插入新元素
    int i = *len;
    while (i > 1 && A[i / 2] < A[i]) { // 向上调整
        swap(&A[i / 2], &A[i]);
        i /= 2;
    }
}

// 删除大根堆的根节点
void DeleteMax(int A[], int *len) {
    A[1] = A[(*len)--]; // 用最后一个元素替换根节点,并减少堆的大小
    HeadAdjust(A, 1, *len); // 调整堆
}

// 打印堆的结构
void PrintHeap(int A[], int len) {
    for (int i = 1; i <= len; i++) {
        printf("%d ", A[i]);
    }
    printf("\n");
}


// 打印堆的结构(水平树格式)
void PrintHeapStructure(int A[], int len) {
    int levels = (int)log2(len) + 1; // 计算堆的层数
    int maxWidth = (1 << (levels - 1)) * 3; // 每层最大宽度

    for (int level = 0; level < levels; level++) {
        int numElements = 1 << level; // 当前层的元素数量
        int spaceBetween = maxWidth / numElements; // 当前层元素之间的间隔

        for (int i = 0; i < numElements && (numElements + i - 1) < len; i++) {
            int index = numElements + i; // 元素的实际索引
            printf("%*s%d", spaceBetween / 2, "", A[index]); // 打印元素
        }
        printf("\n");
    }
}

int main() {
    int A[100] = {0, 6, 2, 10, 8, 7, 9, 3, 5, 4, 1}; // 注意索引0不使用
    int len = 10;

    printf("Original array:\n");
    PrintHeap(A, len);

    printf("Heap structure:\n");
    PrintHeapStructure(A, len);

    // 堆排序
    HeapSort(A, len);
    printf("Sorted array:\n");
    PrintHeap(A, len);
    printf("Heap structure:\n");
    PrintHeapStructure(A, len);

    // 插入新元素
    Insert(A, &len, 15);
    printf("After insert 15:\n");
    PrintHeap(A, len);
    printf("Heap structure:\n");
    PrintHeapStructure(A, len);

    // 删除堆顶元素
    DeleteMax(A, &len);
    printf("After deleting root:\n");
    PrintHeap(A, len);

    printf("Heap structure:\n");
    PrintHeapStructure(A, len);

    return 0;
}

4.归并、基数、计数排序

4.1 归并排序

  • 定义

    • 归并:把两个或多个已经有序的序列合并成一个。

    • 归并的方法:

      1.创建一个长序列,能放下已有的两个序列;

      2.i,j分别指向两个有序序列,k指向长序列;

      3.对比i,j所指元素,更小的先加入长序列,并且更小的元素所在指针加一;

      4.如果i或j已经遍历玩,往长序列中直接添加剩余元素。

      • 注:多路归并也一样,m路归并,每选出一个元素要对比m-1次。

  • 归并排序

    • 手算模拟:

    • 核心操作:把两个有序的队列合并成一个
  • 代码

    int *B=(int*)malloc(n*sizeof(int)); //辅助数组
    
    //mid左右两边各自有序,将两部分归并
    void Merge(int A[],int low,int mid,int high){
        int i,j,k;
        for(k=low;k<=high;k++){//将A中所有元素复制到B(使最后正确排序还是在A中)
            B[k]=A[k]; 
        }
        for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
            if(B[i]<=B[j]){ //较小值复制到A中
                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,high); //对右半部分递归排序
            Merge(A,low,high); //归并
        }
    }
    
  • 算法效率分析

    • 可把归并过程看成2路归并的归并树——形态上是一棵倒立的二叉树。

    • n个元素进行2路归并排序,归并趟数 = ⌈ l o g 2 n ⌉ =\lceil log_2n \rceil =log2n

      每趟归并时间复杂度为 O ( n ) O(n) O(n),则算法时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

    • 空间复杂度= O ( n ) O(n) O(n)

    • 稳定性:稳定。

4.2 基数排序

  • 例子

    原始序列

    image-20240525152335161

    1.按各位元素排列

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 定义

  • 基数排序得到递减序列过程如下:

    1.初始化:设置r个空队列

    2.按各个关键字位权重递增的次序(个、十、百),对d个关键字位分别做分配和收集。

    ​ 1)分配:顺序扫描各个元素,若当前处理的关键字位等于x,则将元素插入队尾。

    ​ 2)收集:把各队列中的结点依次出队并连接。

  • 算法效率分析

    基数排序通常基于链式存储实现。

    • 需要r个辅助队列,空间复杂度 O ( r ) O(r) O(r)
    • 一趟分配 O ( n ) O(n) O(n),一趟收集 O ( r ) O(r) O(r),总共d趟分配、收集,总时间复杂度 O ( d ( n + r ) ) O(d(n+r)) O(d(n+r))
  • 基数排序完整代码

    #include <stdio.h>
    #include <stdlib.h>
    
    // 定义元素类型
    typedef int ElemType;
    
    // 定义链表节点结构
    typedef struct LinkNode {
        ElemType data;
        struct LinkNode *next;
    } LinkNode, *LinkList;
    
    // 定义队列结构
    typedef struct {
        LinkNode *front, *rear;
    } LinkQueue;
    
    // 初始化队列
    void InitQueue(LinkQueue *q) {
        q->front = q->rear = (LinkNode *)malloc(sizeof(LinkNode));
        if (!q->front) {
            printf("Memory allocation failed\n");
            exit(1);
        }
        q->front->next = NULL;
    }
    
    // 判断队列是否为空
    int IsEmpty(LinkQueue q) {
        return q.front == q.rear;
    }
    
    // 入队
    void EnQueue(LinkQueue *q, ElemType e) {
        LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode));
        if (!p) {
            printf("Memory allocation failed\n");
            exit(1);
        }
        p->data = e;
        p->next = NULL;
        q->rear->next = p;
        q->rear = p;
    }
    
    // 出队
    int DeQueue(LinkQueue *q, ElemType *e) {
        if (IsEmpty(*q)) return 0;
        LinkNode *p = q->front->next;
        *e = p->data;
        q->front->next = p->next;
        if (q->rear == p) q->rear = q->front;
        free(p);
        return 1;
    }
    
    // 获取数组中最大元素的位数
    int GetMaxDigits(ElemType arr[], int n) {
        int max = arr[0];
        for (int i = 1; i < n; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
    
        int digits = 0;
        while (max > 0) {
            digits++;
            max /= 10;
        }
        return digits;
    }
    
    // 获取指定位置的数字
    int GetDigit(ElemType num, int pos) {
        for (int i = 0; i < pos; i++) {
            num /= 10;
        }
        return num % 10;
    }
    
    // 基数排序函数
    void RadixSort(ElemType arr[], int n) {
        int maxDigits = GetMaxDigits(arr, n);
    
        // 初始化10个队列用于每个基数位的分配
        LinkQueue queues[10];
        for (int i = 0; i < 10; i++) {
            InitQueue(&queues[i]);
        }
    
        // 对每个位数进行分配和收集
        for (int d = 0; d < maxDigits; d++) {
            // 分配阶段:将元素按当前位的值分配到对应的队列
            for (int i = 0; i < n; i++) {
                int digit = GetDigit(arr[i], d);
                EnQueue(&queues[digit], arr[i]);
            }
    
            // 收集阶段:将各个队列的元素依次出队放回数组
            int index = 0;
            for (int i = 0; i < 10; i++) {
                ElemType e;
                while (DeQueue(&queues[i], &e)) {
                    arr[index++] = e;
                }
            }
    
            // 打印当前步骤的排序结果
            printf("After sorting by digit %d: ", d + 1);
            for (int i = 0; i < n; i++) {
                printf("%d ", arr[i]);
            }
            printf("\n");
        }
    }
    
    int main() {
        ElemType arr[] = {520, 211, 438, 888, 007, 111, 985, 666, 996, 233,168};
        int n = sizeof(arr) / sizeof(arr[0]);
    
        printf("Original array: ");
        for (int i = 0; i < n; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    
        RadixSort(arr, n);
    
        printf("Sorted array: ");
        for (int i = 0; i < n; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    
        return 0;
    }
    

4.3 计数排序

  • 定义

    对每个待排序元素x,统计小于x的元素个数,利用信息可以确定x的最终位置。

  • 代码

    //A[]是输入序列
    //B[]是输出序列
    //C[]存储计数值
    void CountSort(ElemType A[],ElemType B[],int n,int k){
        int i,C[k];
        for(i=0;i<k;i++){ //初始化计数数组
            C[i]=0;
        }
        for(i=0;i<n;i++){ //统计每个元素出现次数
            C[A[i]]++;
        }
        for(i=1;i<k;i++){
            C[i]=C[i]+C[i-1]; //C中存小于等于i的个数
        }
        for(i=n-1;i>=0;i--){
            B[C[A[i]-1]]=A[i]; //将元素A[i]放在输出数组B的正确位置上
            C[A[i]]=C[A[i]]-1;
        }
    }
    

5.内部排序算法的比较

  • 内部排序就是在内存中进行的,以上都是内部排序。

  • 比较

    算法种类最好时间复杂度最坏时间复杂度平均情况空间复杂度是否稳定
    直接插入排序 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)
    冒泡排序 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)
    简单选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)
    希尔排序 O ( 1 ) O(1) O(1)
    快速排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( l o g 2 n ) O(log_2n) O(log2n)
    堆排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( 1 ) O(1) O(1)
    二路归并排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n ) O(n) O(n)
    基数排序 O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( d ( n + r ) ) O(d(n + r)) O(d(n+r)) O ( r ) O(r) O(r)

*完整代码 排序

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

// 交换
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

//--------------------插入排序--------------------
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; // 插入
        }
    }
}

//--------------------折半插入排序--------------------
void InsertHaftSort(int A[], int n) {
    int i, j, low, high, mid;
    for (i = 1; i < n; i++) {
        int temp = A[i];
        low = 0; high = i - 1; // 折半查找范围
        while (low <= high) {
            mid = (low + high) / 2;
            if (A[mid] > temp)
                high = mid - 1; // 查找左子表
            else
                low = mid + 1; // 查找右子表
        }
        for (j = i - 1; j >= low; j--)
            A[j + 1] = A[j]; // 后移元素,空出插入位置
        A[low] = temp; // 插入
    }
}

//--------------------希尔排序--------------------
void ShellSort(int A[], int n) {
    int d, i, j;
    // A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
    for (d = n / 2; d >= 1; d /= 2) { // 步长变化
        for (i = d; i < n; i++) {
            if (A[i] < A[i - d]) { // 需将A[i]插入有序增量子表
                int temp = A[i];
                for (j = i - d; j >= 0 && temp < A[j]; j -= d) {
                    A[j + d] = A[j]; // 记录后移
                }
                A[j + d] = temp; // 插入
            }
        }
    }
}

//--------------------冒泡排序--------------------
void BubbleSort(int A[], int n) {
    for (int i = 0; i < n - 1; i++) {
        bool flag = false; // 表示本趟是否发生交换
        for (int j = n - 1; j > i; j--) {
            if (A[j - 1] > A[j]) { // 若为逆序
                swap(&A[j - 1], &A[j]);
                flag = true;
            }
        }
        if (flag == false) return; // 本趟遍历没有发生交换,说明已经有序
    }
}

//--------------------快速排序--------------------
int Partition(int A[], int low, int high) {
    int pivot = A[low]; // 第一个元素作为基准
    while (low < high) {
        while (low < high && A[high] >= pivot) // high指针不断左移,直到找到比基准元素小的
            high--;
        A[low] = A[high]; // 比基准小的元素左移
        while (low < high && A[low] <= pivot) // low指针不断右移,直到找到比基准元素大的
            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); // 划分右子表
    }
}

//--------------------选择排序--------------------
void SelectSort(int A[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i;
        for (int j = i + 1; j < n; j++) // 选择最小的
            if (A[j] < A[min]) min = j;
        if (min != i) swap(&A[i], &A[min]);
    }
}

//---------------------堆排序--------------------
void HeapAdjust(int A[], int k, int len) {
    int temp = A[k]; // A[0]暂存子树根结点
    for (int i = 2 * k + 1; i < len; i = 2 * i + 1) { // 沿key较大的子结点向下筛选
        if (i + 1 < len && A[i] < A[i + 1]) // 取key较大的子结点的下标
            i++;
        if (temp >= A[i])
            break; // 筛选结束
        else {
            A[k] = A[i]; // 将A[i]调整到双亲结点上
            k = i; // 修改k值,以便继续向下筛选
        }
    }
    A[k] = temp; // 被筛选结点的值放入最终位置
}

// 建立大根堆
void BuildMaxHeap(int A[], int len) {
    for (int i = len / 2 - 1; i >= 0; i--) // 从后往前调整所有非终端结点
        HeapAdjust(A, i, len);
}

// 堆排序完整逻辑
void HeapSort(int A[], int len) {
    BuildMaxHeap(A, len); // 初始建堆
    for (int i = len - 1; i > 0; i--) {
        swap(&A[i], &A[0]); // 堆顶和堆底元素交换
        HeapAdjust(A, 0, i); // 把剩余的待排序元素整理成堆
    }
}

//---------------------归并排序--------------------
void Merge(int A[], int B[], int low, int mid, int high) {
    int i, j, k;
    for (k = low; k <= high; k++) { // 将A中所有元素复制到B
        B[k] = A[k];
    }
    for (i = low, j = mid + 1, k = low; i <= mid && j <= high; k++) {
        if (B[i] <= B[j]) { // 较小值复制到A中
            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 B[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2; // 从中间划分
        MergeSort(A, B, low, mid); // 对左半部分归并排序
        MergeSort(A, B, mid + 1, high); // 对右半部分归并排序
        Merge(A, B, low, mid, high); // 归并
    }
}

int main() {
    int A[] = {34, 8, 64, 51, 32, 21};
    int n = sizeof(A) / sizeof(A[0]);

    printf("Insertion Sort:\n");
    InsertSort(A, n);
    for (int i = 0; i < n; i++) printf("%d ", A[i]);
    printf("\n");

    int B[] = {34, 8, 64, 51, 32, 21};
    printf("Binary Insertion Sort:\n");
    InsertHaftSort(B, n);
    for (int i = 0; i < n; i++) printf("%d ", B[i]);
    printf("\n");

    int C[] = {34, 8, 64, 51, 32, 21};
    printf("Shell Sort:\n");
    ShellSort(C, n);
    for (int i = 0; i < n; i++) printf("%d ", C[i]);
    printf("\n");

    int D[] = {34, 8, 64, 51, 32, 21};
    printf("Bubble Sort:\n");
    BubbleSort(D, n);
    for (int i = 0; i < n; i++) printf("%d ", D[i]);
    printf("\n");

    int E[] = {34, 8, 64, 51, 32, 21};
    printf("Quick Sort:\n");
    QuickSort(E, 0, n - 1);
    for (int i = 0; i < n; i++) printf("%d ", E[i]);
    printf("\n");

    int F[] = {34, 8, 64, 51, 32, 21};
    printf("Selection Sort:\n");
    SelectSort(F, n);
    for (int i = 0; i < n; i++) printf("%d ", F[i]);
    printf("\n");

    int G[] = {34, 8, 64, 51, 32, 21};
    printf("Heap Sort:\n");
    HeapSort(G, n);
    for (int i = 0; i < n; i++) printf("%d ", G[i]);
    printf("\n");

    int H[] = {34, 8, 64, 51, 32, 21};
    int *tempB = (int *)malloc(n * sizeof(int));
    printf("Merge Sort:\n");
    MergeSort(H, tempB, 0, n - 1);
    for (int i = 0; i < n; i++) printf("%d ", H[i]);
    printf("\n");

    free(tempB);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1700577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA:Spring Boot整合MyBatis Plus持久层

1、简述 MyBatis Plus是MyBatis的增强工具包&#xff0c;它在MyBatis的基础上进行了扩展&#xff0c;提供了许多便捷的功能&#xff0c;例如通用CRUD操作、分页插件、代码生成器等。使用MyBatis Plus&#xff0c;开发者可以更加方便地进行持久层操作&#xff0c;并且减少了很多…

Python爬虫实战:利用代理IP获取电商数据

文章目录 1.电商数据介绍2.爬取目标3.代理IP推荐4.准备工作4.1 模块安装4.2 代理IP获取 5.爬虫代码实战5.1分析网页5.1.1 获取cookie5.1.2 关键词分析5.1.3 翻页分析5.1.4 数据获取分析 5.2 发送请求5.3 提取数据5.4 保存数据5.5 完整源码5.6 数据分析六、总结 1.电商数据介绍 …

[随笔] 在CSDN的6周年纪念日随笔

纪念 转眼已过6年&#xff0c;大一的时候学习编程&#xff0c;潜水 CSDN 学习各类博文&#xff0c;才学浅薄就没有主动写博文记录自己的学习历程。 过了段时间刚刚到了大二&#xff0c;很喜欢 Todolist&#xff0c;意气风发的写下《一份清爽的编程计划》&#xff0c;哈哈。 …

冷冻式压缩空气干燥机常见几种系统原理图

冷冻式压缩空气干燥机 我们以两种典型的设计流程图为例 1.干式蒸发型&#xff0c;这类冷干机是我们最为常见的设计型式。下图为deltech公司的典型流程图 此类设备各家设计不同的最大区别基本就是在换热器的结构型式上有比较大的区别。换热器主要有&#xff1a;管壳式、铝板换、…

【openlayers系统学习】00官网的Workshop介绍

00Workshop介绍 官方文档&#xff1a;https://openlayers.org/workshop/en/ openlayers官网Workshop学习。 通过官网Workshop&#xff0c;系统学习openlayers的使用。 基本设置 这些说明假定您从最新Workshop版本的 openlayers-workshop-en.zip​ 文件开始。此外&#xff…

KingbaseES数据库merge语法

数据库版本&#xff1a;KingbaseES V008R006C008B0014 简介 MERGE 语句是一种用于数据操作的 SQL 语句&#xff0c;它能够根据指定的条件将 INSERT、UPDATE 和 DELETE 操作结合到单个语句中。其主要作用是在目标表和源表之间进行数据比较和同步&#xff0c;根据条件的匹配情况来…

IBERT眼图扫描(高速收发器八)

前文讲解了GTX的时钟及收发数据通道的组成&#xff0c;之后讲解了眼图、加重、均衡等原理及原因&#xff0c;本文通过xilinx提供的IBERT IP完成实际工程的眼图扫描&#xff0c;确定加重和幅值调节的参数。 1、回环模式 在此之前&#xff0c;需要了解一下GTX的回环模式。如果板…

批处理作业调度问题 (回溯法)

目录 一、问题解析 二、实例剖析 三、算法思路 四、代码实现 结果&#xff1a; 总结 前言 【问题】n 个作业{1, 2, …, n}要在两台机器上处理&#xff0c;每个作业必须先由机器 1 处理&#xff0c;再由机器 2 处理&#xff0c;机器 1 处理作业i所需时间为 ai&#xff0c;…

【Unity实战篇 】| Unity实现 文本框可以自适应大小,到达最大宽度之后再缩小字体

前言 在文本框可以自适应大小拉伸的前提下,增加一个最大限制宽度,使其到达最大宽度后 再启用 Best Fit 实现自适应改变文字大小以适应文本框的大小。 【Unity实战篇 】 | Unity实现 Text文本框可以自适应大小,到达最大宽度之后再缩小字体 在Unity中经常会用到文本组件的自…

Jmeter插件下载(下载和使用教程)

插件管理器&#xff1a;plugins-manager下载安装和使用 下载&#xff1a; 官网地址&#xff1a;https://jmeter-plugins.org/install/Install/ 步骤1&#xff1a;将下载jmeter-plugins-manager-1.10.jar放到目录apache-jmeter-5.1.1\lib\ext&#xff0c;如下图 步骤2&#x…

《最新出炉》系列入门篇-Python+Playwright自动化测试-42-强大的可视化追踪利器Trace Viewer

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 在我们日常执行自动化测试工作的过程中&#xff0c;经常会遇到一些偶发性的bug&#xff0c;但是因为bug是偶发性的&#xff0c;我们不一定每次执行都能复现&#xff0c;所以我…

力扣刷题--747. 至少是其他数字两倍的最大数【简单】

题目描述 给你一个整数数组 nums &#xff0c;其中总是存在 唯一的 一个最大整数 。 请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是&#xff0c;则返回 最大元素的下标 &#xff0c;否则返回 -1 。 示例 1&#xff1a; 输入&#xff1a;n…

Android端 可使用Yolov5训练 路标识别

相信大家对于路标识别&#xff0c;红绿灯识别&#xff0c;图形识别opencv中也是一件烦人的事情&#xff0c;其参数是及其受到现实环境因素的影响的&#xff0c;那么今天我就给大家推荐一种方式&#xff0c;缺点是周期长&#xff0c;但其优点是如果训练效果好久对于环境的各种变…

河南乙级道路与桥梁资质年审材料准备要点解析

河南乙级道路与桥梁资质年审材料准备要点解析如下&#xff1a;河南宽信权经理 一、企业基本情况材料 营业执照副本复印件&#xff1a;确保复印件清晰、完整&#xff0c;并加盖企业公章。 企业章程&#xff1a;提供最新的企业章程&#xff0c;并加盖企业公章。此材料需反映企业…

matepad11和matepad2023区别

​ 华为MatePad11和MatePad2023是华为公司推出的两款平板电脑&#xff0c;它们在处理器、价格和屏幕等方面存在差异。具体分析如下&#xff1a; 处理器 MatePad 11英寸2023款&#xff1a;搭载骁龙865处理器&#xff0c;最高主频为2.84GHz。 MatePad 2023&#xff1a;配备骁…

Android:使用Kotlin搭建MVVM+组件化App架构

一、简介Android MVVM架构模式 请点击查看Android&#xff1a;使用Kotlin搭建MVVM架构模式 二、简介组件化 以前一个项目所有业务代码都在App&#xff08;model&#xff09;里开发&#xff0c;App里按照功能模块分包&#xff0c;例如&#xff1a;首页&#xff08;home&#xff…

58同城前端社招一面挂了,面试总结:coding能力有待提高

总结面试不足&#xff1a;coding能力有待提高&#xff0c;项目对于大厂面试来说不够有亮点&#xff0c;难度不够&#xff0c;对于技术细节不够深入。下周继续加油&#xff0c;噢力给给&#xff01;&#xff01;&#xff01;&#x1f62d;&#x1f62d;&#x1f62d; 面试题 1、…

[Algorihm][简单多状态DP问题][买卖股票的最佳时机含冷冻期][买卖股票的最佳时机含手续费]详细讲解

目录 1.买卖股票的最佳时机含冷冻期1.题目链接买卖股票的最佳时机含冷冻期2.算法原理详解3.代码实现 2.买卖股票的最佳时机含手续费1.题目链接2.算法原理详解3.代码实现 1.买卖股票的最佳时机含冷冻期 1.题目链接 买卖股票的最佳时机含冷冻期 2.算法原理详解 思路&#xff…

Realcome:AI赋能,引领虚拟博主创业新纪元

在数字化浪潮汹涌的时代&#xff0c;一个崭新的科技品牌正在崭露头角——Realcome&#xff0c;一家以AI智能交互服务为核心&#xff0c;为企业提供全方位数字化营销解决方案的科技服务公司。自2021年成立以来&#xff0c;Realcome凭借其前沿的技术和创新的理念&#xff0c;迅速…

漂流瓶挂机项目,聊天脚本赚钱新玩法,号称单机30-50+ (教程+软件)

一、项目简介&#xff1a; 漂流瓶挂机项目主要是通过使用探遇漂流瓶、音麦漂流瓶等聊天软件&#xff0c;为用户提供一个聊天赚钱的平台。男性用户需要充值后才能发送消息&#xff0c;而女性用户则可以通过接收消息赚取分红。男性用户发送给女性用户的消息费用大约在.1-.2元之间…