9 > 数据结构与算法 排序

本节目录

概念

  1. 算法稳定性:相同元素在排序后相对位置没有变化,则称算法是稳定的。
  2. 内部排序:数据全放入内存中再进行排序。
  3. 外部排序:数据太多,无法全部放入内存中进行排序。
  4. 稳定算法:插入排序、冒泡排序、并归排序、(桶、计数)基数排序

插入排序

  1. 空间复杂度: O ( 1 ) O(1) O(1)
  2. 时间复杂度: 最好 : O ( n ) 最坏 : O ( n 2 ) 平均时间复杂度 : O ( n 2 ) 最好:O(n)~~~~最坏:O(n^2)~~~~平均时间复杂度:O(n^2) 最好:O(n) 最坏:O(n2) 平均时间复杂度:O(n2)
  3. 稳定性:稳定
  4. 优化:采用二分法
c 复制代码
int a[10]={2,42,42,1,64,35,6,32,4354,78};
void InsertSort(int *suzu){       //插入排序
    for (int i=1;i<10;i++){
        int a=suzu[i],j=i;
        if (suzu[i]<suzu[i-1]){
            for (;j>=0 && j-1>=0;j--){
                if (a>=suzu[j-1])
                    break;
                if (a<suzu[j-1])
                    suzu[j]=suzu[j-1];
            }
            suzu[j]=a;
        }
    }
}

希尔排序

  1. 步骤:将表划分为若干子表,每个子表执行插入排序。建议第一次选择 ⌊ 总元素数 / 2 ⌋ \lfloor总元素数/2\rfloor ⌊总元素数/2⌋ 来作为增量,以后每一次都/2作为增量。(可以缩短增量 d)
  2. 空间复杂度: O ( 1 ) O(1) O(1)
  3. 时间复杂度:
    1. 最坏情况: O ( n 2 ) ( 直接退化成插入排序 ) 当 n 在某个范围内 : O ( n 1.3 ) O(n^2)~~_{(直接退化成插入排序)}~~~当n在某个范围内:O(n^{1.3}) O(n2) (直接退化成插入排序) 当n在某个范围内:O(n1.3)
    2. 最优情况: O ( N log ⁡ 2 N ) O(N\log_2N) O(Nlog2N)
  4. 稳定性:不稳定
  5. 适用性:适用于顺序表,不适用于链表
c 复制代码
void ShellSort(int *suzu){      //希尔排序
    for (int k=10/2;k>=1;k/=2) {
        for (int i=k;i<10;i++){
            int a=suzu[i],j=i;
            if (suzu[i]<suzu[i-k]){
                for (;j>=0 && j-k>=0;j-=k){
                    if (a>=suzu[j-k])
                        break;
                    if (a<suzu[j-k])
                        suzu[j]=suzu[j-k];
                }
                suzu[j]=a;
            }
        }
    }
}

冒泡排序

  1. 空间复杂度: O ( 1 ) O(1) O(1)
  2. 时间复杂度: 最好 : O ( n ) 最坏 : O ( n 2 ) 平均 : O ( n 2 ) 最好:O(n)~~~最坏:O(n^2)~~~~平均:O(n^2) 最好:O(n) 最坏:O(n2) 平均:O(n2)
  3. 稳定性:稳定
  4. 适用:顺序表、链表
c 复制代码
void BubbleSort( int *suzu){        //冒泡排序
    for (int i=0;i<10-1;i++){
        int sign=false;
        for (int j=0;j<10-1-i;j++){
            if (suzu[j]>suzu[j+1]){
                int a=suzu[j];
                suzu[j]=suzu[j+1];
                suzu[j+1]=a;
                sign=true;
            }
        }
        if (sign==false)
            return;
    }
}

//鸡尾酒排序算法
void Swap(int *a,int *b){
    int c=*a;
    *a=*b;
    *b=c;
}

void Logic(int *array){
    int len=8;
    int front=0,rear=len-1;
    _Bool tag=false;      //false代表未完成排序
    while (front<rear && tag==false) {
        tag=true;
        for (int i = front; i < rear; i++) {
            if (array[i] > array[i + 1]) {
                Swap(&array[i], &array[i + 1]);
                tag = false;
            }
        }
        rear--;
        for (int i = rear; i > front; i--) {
            if (array[i] < array[i - 1]) {
                Swap(&array[i], &array[i - 1]);
                tag = false;
            }
        }
        front++;
    }

}

快速排序

  1. 算法步骤:( "画成" 二叉排序树)
    1. 从数列中挑出一个元素,称为 "基准"(pivot);
    2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
    3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
  2. 空间复杂度: O ( 递归深度 ) O(递归深度) O(递归深度) 最好: O ( log ⁡ 2 h ) O(\log_2h) O(log2h) 最坏: O ( h ) ( 树最高 ) O(h)~~~_{(树最高)} O(h) (树最高)
  3. 时间复杂度: O ( n ∗ 递归深度 ) = > 最好、平均 : O ( n log ⁡ 2 n ) 最坏 : O ( n 2 ) O(n*递归深度)=>~~最好、平均:O(n\log_2n)~~最坏:O(n^2) O(n∗递归深度)=> 最好、平均:O(nlog2n) 最坏:O(n2)
  4. 有序序列使用快速排序性能最差,因为树最高,递归深度最大。
  5. 稳定性:不稳定
c 复制代码
int QSort(int *array,int head,int tail){        //快速排序
    int mid=array[head];
    while (head<tail){
        while (array[tail]>=mid && head<tail){
            tail--;
        }
        array[head]=array[tail];

        while (array[head]<=mid && head<tail){
            head++;
        }
        array[tail]=array[head];
    }
    array[head]=mid;

    return head;
}

void QS2(int *array,int head,int tail){     //快速排序递归排序每一个分表
    if (head<tail){
        int a=QSort(array,head,tail);
        QS2(array,head,a-1);
        QS2(array,a+1,tail);
    }
}

void QuickSort( int *array){
    int head=0,tail=9;
    QS2(array,head,tail);
}

直接选择排序

  1. 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  2. 空间复杂度: O ( 1 ) O(1) O(1)
  3. 稳定性:不稳定
  4. 适用:顺序表、链表
c 复制代码
void SimpleSelect( int *array){      //直接快速排序
    for (int i=0;i<10-1;i++){
        for (int j=i+1;j<10;j++){
            if (array[i]>array[j]){
                int a=array[i];
                array[i]=array[j];
                array[j]=a;
            }
        }
    }
}

简单选择排序

  1. 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  2. 空间复杂度: O ( 1 ) O(1) O(1)
  3. 稳定性:不稳定
  4. 适用:顺序表、链表
c 复制代码
void swap(int *a,int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}
void selection_sort(int arr[], int len){
    int i,j;
    for (i = 0 ; i < len - 1 ; i++){
		    int min = i;
        for (j = i + 1; j < len; j++){  
		        if (arr[j] < arr[min]) 
		            min = j;
            swap(&arr[min], &arr[i]); 
        }
		}
}

堆排序(选择排序 Pro)

  1. 堆:(利用完全二叉树)
    1. 大顶堆:所有节点元素都 大于 它的左右子树节点的值。
    2. 小顶堆:所有节点元素都 小于 它的左右子树节点的值。
  2. 详情:https://www.runoob.com/w3cnote/heap-sort.html
  3. 建堆过程:关键字对比次数不超过 4 n 4n 4n,建堆时间复杂度: O ( n ) O(n) O(n)
  4. 时间复杂度: O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)
  5. 空间复杂度: O ( 1 ) O(1) O(1)
  6. 稳定性:不稳定
  7. 堆插入元素:将新插入元素放到堆尾,然后调整成大(小)顶堆。
  8. 堆删除元素:用堆低元素替代被删除元素,然后调整成大(小)顶堆。
  9. 每上升一次对比一次关键字,每下降一次可能对比一次、可能对比两次关键字。
c 复制代码
void BigHeapSort( int *array){          //堆排序
    for (int k=(10-1);k>=0;k--){
        for (int i = k / 2; i >= 0; i--) {
            int max = array[i];
            for (int j= i*2;j<= k;j*=2) {
                if (array[j] < array[j + 1] && j < k) {
                    j++;
                }
                if (array[i] < array[j]) {
                    array[i] = array[j];
                    i = j;
                } else
                    break;
            }
            array[i] = max;
        }
        int a=array[0];
        array[0]=array[k];
        array[k]=a;
    }
}

归并排序

  1. 将多个有序数组进行合并。
  2. 归并树:归并排序形态上是倒立的 k 叉树。二叉树第 h 层最多有 k h − 1 k^{h-1} kh−1个节点,若树高为 h ,则 n ≤ k h − 1 n\le k^{h-1} n≤kh−1,归并总次数是 ⌊ log ⁡ k n ⌋ + 1 \lfloor\log_kn\rfloor+1 ⌊logkn⌋+1 (k为归并路数,n为元素个数)。
  3. 每次归并时间复杂度: O ( n ) O(n) O(n)
  4. 时间复杂度: O ( n log ⁡ k n ) ( 每趟归并时间为 n ) O(n\log_kn)~~~_{(每趟归并时间为n)} O(nlogkn) (每趟归并时间为n)
  5. 空间复杂度: O ( n ) O(n) O(n)
  6. 稳定性:稳定
c 复制代码
void TwoMergeSort(int *array1,int *array2){        //二路归并排序
    BubbleSort(array1);
    BubbleSort(array2);

    int len1=5,len2=5;
    int target[10];
    int tar_len=0,point2=0;
    for (int i=0;i<len1;i++){      //array1长度为5
        for (int j=point2;j<len2;j++){     //array2长度为5
            if (array1[i]<=array2[j]){
                target[tar_len++]=array1[i];
                break;
            }
            if (array1[i]>array2[j]){
                target[tar_len++]=array2[j];
                point2++;
            }
        }
        if (point2==len2)
            target[tar_len++]=array1[i];
    }
    while (point2<len2)
        target[tar_len++]=array2[point2++];

}

(桶)基数排序

  1. 桶类型排序区别:
    1. 基数排序:根据键值的每位数字来分配桶;
    2. 计数排序:每个桶只存储单一键值;
    3. 桶排序:每个桶存储一定范围的数值。
  2. 步骤:
    1. 初始化队列个数(r):有多少种类型的数字就初始化多少个队列,一般初始化10个;
    2. 每一轮依次按照"个十百..."位的大小将元素加入相应队列的队尾;(一趟耗时 n)
    3. 加入完成后依次从每个队头取出所有元素组成新的序列;(一趟耗时 r)
    4. 重复 b。
  3. 分配次数(排序次数):依据整个序列中最大位的元素。
  4. 空间复杂度: O ( r ) O(r) O(r)
  5. 时间复杂度: O ( d ∗ ( n + r ) ) ( d 为排序次数, n 为元素个数, r 为队列个数 ) O(d*(n+r))~~~~~~_{(d为排序次数,n为元素个数,r为队列个数)} O(d∗(n+r)) (d为排序次数,n为元素个数,r为队列个数)
  6. 稳定性:稳定
  7. 适用于:基数关键字可以很方便的拆分成多个小关键字(d较小);每组关键字取值范围不是很多(r较小);需要排序的数据基数很大(n很大)。

外部排序

  1. 进行 k 路并归排序,需要在内存中开辟 k 个输入缓冲区和 1 个输出缓冲区。(缓冲区大小等于磁盘block大小)(采用多路归并可以减少并归趟数,进而减少磁盘IO次数)
  2. 步骤:(总归并次数: ⌊ log ⁡ k n ⌋ + 1 \lfloor\log_kn\rfloor+1 ⌊logkn⌋+1)
    1. 先将每个片段进行内部排序,生成 r 个初始并归片段;
    2. 进行 k 路并归排序。
  3. 优化:
    1. 增加并归路数 k ;(代价:需要增加相应缓冲区;增加了多路并归排序的关键字对比次数)
    2. 减少并归片段 r 数量。(代价:增加了内部归并时间)
  4. 外部排序时间开销:读写外存时间(主要) + 内部排序时间 + 内部并归时间
  5. 什么是 k 路平衡归并:
    1. 最多只能有 k 段归并为一个;
    2. 每趟归并,若有 m 个段参与归并,则经过一趟归并后得到 ⌈ m + k ⌉ \lceil m+k\rceil ⌈m+k⌉ 个新的归并段。
  6. 败者树:(可以减少多路归并中关键字对比次数)(第一次构造败者树需要进行 n-1 次对比)
    1. 对于 n 路并归,最多需要对比关键词次数: ⌈ log ⁡ 2 n ⌉ \lceil\log_2n\rceil ⌈log2n⌉

置换-选择排序

  1. 步骤:
    1. 从文件记录中输入 n 个记录到工作区;(工作区长度为 n)
    2. 找出工作区中最小关键词(MIX)输入到输出缓冲区,并递增式记录当前最小值MIX;
      1. 若当前元素中全部元素都小于MIX,则重新开辟新的并归段,执行 b。
    3. 继续加入文件记录中的待排序元素,执行 b,直到工作区为空。

最佳归并树(哈夫曼树)

  1. 归并中磁盘 I / O = k 路归并树 W P L ∗ 2 ( W P L 为归并树树的带权路径长度之和 ) 归并中磁盘I/O=k路归并树WPL * 2~~~~_{(WPL为归并树树的带权路径长度之和)} 归并中磁盘I/O=k路归并树WPL∗2 (WPL为归并树树的带权路径长度之和)
  2. 归并排序:
  3. 优化:
    1. 减少磁盘IO:构造哈夫曼树以获取最优WPL。(若无法严格构成 k 叉归并树,则需补充 0 补全(虚段,长度为 0,占位用))
  4. k 叉最佳并归树一定是一个严格的 k 叉并归树,设度为 k 的节点有 a 个,度为 0 的节点有 b 个,总的节点数为 c,则:(添加虚段的数量)
相关推荐
算法歌者5 分钟前
[算法]入门1.矩阵转置
算法
林开落L20 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色21 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download22 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
SoraLuna42 分钟前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎1 小时前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
YSRM1 小时前
Experimental Analysis of Dedicated GPU in Virtual Framework using vGPU 论文分析
算法·gpu算力·vgpu·pci直通
韭菜盖饭2 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode