数据结构与算法 | 基于关键词比较的排序

目录

一、插入排序

1.直接插入排序

2.希尔排序

二、交换排序

1.冒泡排序

2.快速排序

三、选择排序

1.直接选择排序

2.堆排序 

四、合并排序


注意:

[1]时间复杂度:O(n)~O(n2)~O(n2)表示最好情况下的时间复杂度~平均情况下的时间复杂度~最坏情况下的时间复杂度

[2]稳定性:如果两条记录关键词相同,经排序后它们的相对次序不变,则称该排序算法是稳定的。例如,对任意两条记录R1和R2,如果它们的关键词K1=K2,并且初始状态下R1在R2前面,那么经稳定的排序算法排序之后R1依然在R2前面。

一、插入排序

1.直接插入排序

算法思想:进行n-1次插入,其中第i次插入是将a[i]插入一个已经排序好的数组a[0..i-1]

算法 直接插入insertSort(待排序数组a[],数组长度n)
S1.[特殊情况处理]
    如果数组长度小于等于1,算法结束.
S2.[初始化]
    i<-1.
S3.[假设a[0],...,a[i-1]已经排好序,将a[i]插入a[0],...,a[i-1]中]
    ends<-i-1.
    m<-a[i].
    WHILE ends>=0 && m<a[ends] DO(
        a[ends+1]<-a[ends].
        ends<-ends-1.
    )
    //将m(原来的a[i])插入a[ends]的后面:
    a[ends+1]<-m.
S3.[i<n?]
    //a[n-1]的插入是否已经完成?
    i<-i+1.
	IF i<n 
        THEN GOTO S3.
    ELSE
        算法结束.
/*直接插入排序,从小到大*/
void insertSort(int a[],int n){
    for(int i=1;i<n;i++){
        int ends=i-1;
        int m=a[i];
        for(;ends>=0&&m<=a[ends];ends--){
            a[ends+1]=a[ends];
        }
        a[ends+1]=m;
    }
    return;
}
/*直接插入排序2,从小到大*/
void insertSort2(int a[],int n){
    for(int j=1;j<n;j++){
        /*把a[j]插入a[0],a[1],...,a[j-1]*/
        int i=j-1;
        int key=a[j];
        //寻找a[j]应该插入的位置:
        while(i>=0&&key<a[i]){
            a[i+1]=a[i];
            i--;
        }
        a[i+1]=key;
    }
    return;
}

2.希尔排序

算法思想:把待排序的记录按步长step进行分组,对每个分组进行直接插入排序;然后按一定的规则减小步长,重复上面的步骤;当步长减小到0时,算法结束

(步长为step的分组为:a[0],a[step],a[step*2],...、a[1],a[1+step],a[1+step*2],...、a[2],a[2+step],a[2+step*2],...、......、a[step-1],a[2*step-1],a[3*step-1],...)

算法 希尔排序shellSort(待排序数组a[],数组长度n)
S1.[计算初始步长]
    step<-n/2.
S2.[对每个步长为step的分组插入排序]
/*
例如当n=16,step=4时,分组如下:
a[0],a[4],a[8],a[12]; a[1],a[5],a[9],a[13]; a[2],a[6],a[10],a[14]; a[3],a[7],a[11],a[15]
先把a[4]插入a[0]、a[5]插入a[1]、a[6]插入a[2]、a[7]插入a[3];
再把a[8]插入a[4],a[0]、a[9]插入a[5],a[1]、a[10]插入a[6],a[2]、a[11]插入a[7],a[3];
……
以此类推,直至每个分组都完成了排序.
*/
S3.[缩短步长]
    step<-step/2.
    IF step>0 THEN GOTO S2.
    ELSE 算法结束.
/*希尔排序,从小到大*/
void shellSort(int a[],int n){
    //步长:
    int step=n/2;
    while(step>0){
        /*对每个步长为step的分组插入排序*/
        for(int j=step;j<n;j++){
            int i=j-step;
            int key=a[j];
            //把a[j]插入a[0],..,a[j-step]:
            while(i>=0&&]key<a[i){
                a[i+step]=a[i];
                i=i-step;
            }
            a[i+step]=key;
        }
        step=step/2;
    }
    return;
}

二、交换排序

1.冒泡排序

算法思想:不断交换序列中的反序对,直到不再有反序对为止

算法 冒泡排序bubbleSort(待排序数组a[],数组长度n)
S1.[假设数组中有反序对]
    exchange<-true.
S2.[判断是否具有反序列]
    先假设没有反序对:exchange<-false.
    如果相邻两元素是反序列,则交换这两个元素,并令exchange<-true.
    如果数组中具有反序列(exchange==true),则跳转到步骤S2;否则结束算法.
//注意:由于每趟冒泡至少能把序列中最大的数找出来,所以不必每趟冒泡都把整个数组遍历一遍
/*冒泡排序,从小到大*/
void bubbleSort(int a[],int n){
    bool exchange = true;
    for(int ends=n-1;ends>0&&exchange;ends--){
        exchange = false;
        for(int i=0;i<ends;i++){
            if(a[i]>a[i+1]){
                int m=a[i];
                a[i]=a[i+1];
                a[i+1]=m;
                exchange=true;
            }
        }
    }
    return;
}

2.快速排序

算法思想:先从序列中选择一个数作为基准,通常选最左边的数(也可以都选最右边的数);每趟排序,都把作为基准的数放到了它在序列中的正确位置,并且这时候基准数将序列划分为了两部分;再分别对这两部分序列作快排

算法 快速排序Qsort(待排序数组a[],数组下届low,数组上界up)
Q1.[递归出口]
    IF low>=up THEN RETURN.
Q2.[寻找基准数在序列中的正确位置]
    Q2.1.取最左边的数a[low]为基准数key. left<-low. right<-up.
    Q2.2.right从右往左找小于key的数
    Q2.3.left从左往右找大于key的数
    Q2.4.交换a[left]和a[right]
    Q2.5.IF left<right THEN GOTO Q2.2
    Q2.6.交换a[left]和a[low]//此时left==right
Q3.[用基准数将序列划分为两部分,对它们分别进行快速排序]
    Qsort(a,low,l-1).
	Qsort(a,l+1,up).
void Qsort(int a[],int low,int up){
    if(low>=up)
        return;
    int l=low,r=up,key=a[low];
    while(l<r){
        //r从右往左找小于key的值
        while(l<r&&a[r]>=key){
            r--;
        }
        //l从左往右找大于key的值
        while(l<r&&a[l]<=key){
            l++;
        }
        int m=a[l];
        a[l]=a[r];
        a[r]=m;
    }
    a[low]=a[l];
    a[l]=key;
    Qsort(a,low,l-1);
    Qsort(a,l+1,up);
}

三、选择排序

1.直接选择排序

算法思想:进行n-1次选择,其中第i次选择是从序列中找出第i小的元素

算法 直接选择selectSort(待排序数组a[],数组长度n)
S1.[初始化]
    i<-0.
S2.[从a[i],...,a[n-1]中找出第i小元素的位置min]
    min<-i.
    FOR j=i+1 TO n-1 DO
        IF a[j]<a[min] THEN min<-j.
    交换a[min]和a[i].
S3.[是否找到倒数第二小的元素]
    IF i<(n-1)
        THEN i<-i+1.GOTO S2.
    ELSE
        算法结束.
/*直接选择排序,从小到大*/
void selectSort(int a[],int n){
    for(int i=0;i<n-1;i++){
        int mins=i;
        for(int j=i+1;j<n;j++){
            if(a[j]<a[mins])
                mins=j;
        }
        if(mins!=i){
            int m=a[i];
            a[i]=a[mins];
            a[mins]=m;
        }
    }
    return;
}

2.堆排序 

算法思想:先构建一个大根堆(父节点比左右儿子节点大的二叉树),大根堆的根节点就是序列中最大的数;交换根节点与最后节点的位置,将最后一个节点(交换位置前的根节点)剔除出大根堆;重建大根堆,之后重复上一步骤,直至大根堆为空

  • 实际编程中用数组存储大根堆就可以了,不需要使用二叉链表
  • 注意:对于第i个节点:
  • 它的父节点为第(i-1)/2个节点;
  • 它的左子节点为第2i+1个节点;
  • 它的右子节点为第2i+2个节点。
算法 堆排序heapSort(待排序数组a[],数组长度n)
H1.[构建大根堆]
    /*因为包含一个节点的二叉树是大根堆,所以我们从最后一个节点a[n-1]的父节点a[(n-2)/2]开始,把以a[(n-2)/2]为根节点的二叉树恢复成大根堆*/
    FOR i=(n-2)/2 TO 0 DO
        restore(a,i,n).
H2.[排序]
    length<-n-1.
    //如果大根堆不为空,就取出根节点,然后重建大根堆:
    WHILE length>0 DO(
        交换a[length]和a[0].
        restore(a,0,length).//使包含length个节点的二叉树重新成为大根堆
        length<-length-1.
    )

算法 重建堆restore(待重建的大根堆a[],根节点下标root,节点个数length)
/*假设a[length]原来是一个大根堆,因为根节点是大根堆中最大的数,我们交换了大根堆的根节点和最后一个节点的位置,并且使大根堆的节点数减1。这时a[length-1]可能不再是一个大根堆了,restore要做的就是把a[length-1]重新恢复成一个大根堆*/
R1.[初始化]
    j<-root
R2.[建堆]
    //只要a[j]不是a[length]的父节点:
    WHILE j<=(length-1)/2 DO(
        求a[j]的左右子节点的最大值a[m].
        IF a[j]<a[m] 
            THEN 交换a[j]和a[m].
        ELSE
            j<-length.//a已经是大根堆了,算法结束.
    )
/*swapa:交换数组a下标为i、j的元素*/
void swapa(int a[],int i,int j){
    int t=a[i];
    a[i]=a[j];
    a[j]=t;
}
/*restore:重建堆*/
void restore(int a[],int root,int length){
    int j=root;
    while((j<=(length-1)/2)){
        /*求a[j]的左右子节点的最大值a[m]*/
        int m=2*j+1;//取a[m]为a[j]的左子节点
        if(m+1<length&&a[m]<a[m+1])
            m++;//右子节点比左子节点大
        /*a[j]与a[m]比较*/
        if(m<length&&a[j]<a[m]){
            swapa(a,j,m);
            j=m;
        }else{
            j=length;
        }
    }
    return;
}
/*堆排序,从小到大*/
void heapSort(int a[],int n){
    //构建大根堆:
    for(int i=(n-1)/2;i>=0;i--)
        restore(a,i,n);
    //重建堆:
    int length=n-1;
    while(length>0){
        swapa(a,0,length);
        restore(a,0,length);
        length=length-1;
    }
    return;
}

四、合并排序

算法思想:对长度为n的序列,将其划分为n个文件,合并相邻的两个文件(合并后的文件应是有序的);此时共有(n+1)/2个文件,再次合并相邻的两个文件;以此类推,直至剩下两个文件合并成一个文件之后,算法结束。

 注意:合并排序需要使用额外的空间  

算法 文件合并Merge(源文件a[],i,i,n,目标文件x[])
/*假设数组a[i],a[i+1],...,a[j-1]部分以及a[j],a[j+1]...,a[n-1]部分都已经排好序了,现将文件1 (a[i],a[i+1],...,a[j-1]) 以及文件2 (a[j],a[j+1]...,a[n-1]) 按从小到大的顺序合并到文件x[i],...,x[n-1]中*/
M1.[初始化]
    j0<-j.counts<-i. 
M2.[合并文件1和文件2到文件x中,直至有一个文件为空]
M3.[将文件1或文件2剩下的内容全部复制到文件x中]

算法 一趟合并MPass(源文件a[],步长l,文件长度n,目标文件x[])
M1.[初始化]
    i<-0.
M2.[合并相邻两个长度都为l的子文件]
    WHILE i<n DO(
        Merge(a,i,i+l,i+2*l,x).//合并a[i],a[i+1],...,a[i+l-1]和a[i+l],...,a[i+2l-1]
        i<-i+l*2.
    )
M3.[合并剩余的文件]
    IF i+l<n THEN Merge(a,i,i+l,n,x)
    ELSE IF i<n THEN WHILE i<n DO( x[i]<-a[i].i++. )

算法 合并排序MSort(源文件a[],文件长度n,目标文件x[])
M1.[初始化步长]
    l<-1.
M2.[交替合并长度为l、2l的文件]
    MPass(a,l,n,x).
    l<-l*2.
    MPass(x,l,n,a).
    l<-l*2.
M3.[l<n?]
    IF l<n THEN GOTO M2.
    ELSE 算法结束.
/*
 *-合并相邻的两个文件Merge-
 *文件1下标为i,i+1,...,j-1;文件2下标为j,j+1,...,n-1
 *x为辅助空间
 */
void Merge(int a[],int i,int j,int n,int x[]){
    int j0=j;
    int counts=i;//计数
    while(i<j0&&j<n){
        if(a[i]<a[j]){
            x[counts]=a[i];
            i++;
            counts++;
        }else{
            x[counts]=a[j];
            j++;
            counts++;
        }
    }
    while(i<j0){
        x[counts]=a[i];
        i++;
        counts++;
    }
    while(j<n){
        x[counts]=a[j];
        j++;
        counts++;
    }
    return;
}
/*
 *-一趟合并MPass-
 *将长度为l(或小于l)的子文件合并
 */
void MPass(int a[],int l,int n,int x[]){
    int i=0;
    if(l>=n){
        //如果l>=n,就把a复制给x
        for(i=0;i<n;i++)
            x[i]=a[i];
        return;
    }
    /*合并相邻的长度为l的子文件*/
    for(i=0;i<n;i+=2*l){
        Merge(a,i,i+l,i+2*l,x);
    }
    /*合并剩余长度小于2l或l的子文件*/
    if(i<n){
        if(i+l<n){
            //剩余长度<2*l,但>l
            Merge(a,i,i+l,n,x);
        }else{
            //剩余长度<=l
            for(;i<n;i++){
                x[i]=a[i];
            }
        }
    }
    return;
}
/*
 *-合并排序MSort,从小到大-
 *由于使用了两趟交替合并,排序的结果在a[]中
 */
void MSort(int a[],int n,int x[]){
    int l=1;
    while(l<n){
        MPass(a,l,n,x);
        l*=2;
        MPass(x,l,n,a);
        l*=2;
    }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

易水卷长空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值