【C语言督学训练营 第十七天】考研中常考的排序大题(下)---- 选择排序、归并排序、堆排序

前言

今天介绍的内容有选择排序、归并排序、堆排序,整体来说的话不难,在实际写代码题的时候需要注意一些细节问题,还有一件事需要引起我们的注意,在考研时如果考到了排序类型的代码大题,通常会按照时间复杂度、空间复杂度给分如果选择一个非常合适的算法,那么这两个复杂度是肯定可以轻松达到的,所以我们在写题时要稳中求精,如果没办法精准打击就选择一个自己会的,退而求其次推荐熟练掌握快速排序算法,如果觉得这几个算法太难理解可以只熟悉代码,但一定要理解算法的思想,做到写不出但可以看懂!下面就开始今天的内容,先来看一下真题。
在这里插入图片描述
这个题使用快排或者堆排就可以达到想要的效果。

一、选择排序

选择排序分为:简单选择排序、堆排序。
思想:简单选择排序原理:假设排序表为L[1…n],第i趟排序即从L[i…n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n -1趟排序就可使得整个排序表有序。
首先假定第零个元素是最小的,把下标0赋值给min (min 欢取小的儿系的下仍),P公记较时,从1号元素一直比较到9号元素,谁更小,就把它的下标赋给min,一轮比较结束后,将min对应位置的元素与元素i交换,如下表所示。第一轮确认2最小,将2与数组开头的元素3交换。第二轮我们最初认为87最小,经过一轮比较,发现3最小,这时将87与3交换。持续进行,最终使数组有序。

注意点:选择排序与冒泡排序均是两个循环构成,如果不理解两者思想的话容易混淆!需要多加注意。
如果文字有点晦涩难懂那么我们可以看一看动画!
在这里插入图片描述
代码实战:

//选择
//针对选择排序循环条件
//每次找出一个位置,然后拿其余元素与其对比找出目前无序序列中最大或最小的元素放在选出的位置
//需要经过n-1次筛选(所以外层循环为n-1)
//从选出位置的下一个元素开始对比,直到列表中无序部分的末尾(所以内层循环那样写)
//针对算法思想:
//选择排序每次选出一个最大的元素或者最小的元素(看升序还是降序),放在当前无序的部分的第一个位置
//每选出一个就将有序部分向后扩展一位,继续选元素,直到整个序列有序。
void SelectSort(int *p){
    
    
    for(int i=0;i<maxSize-1;i++){
    
    
        for(int j=i+1;j<maxSize;j++){
    
    
            if(p[i]>p[j]){
    
    
                int temp=p[i];
                p[i]=p[j];
                p[j]=temp;
            }
        }
    }
}

二、归并排序

归并排序有很多种,最常见的是内部排序的二路归并,除此之外还有外部排序的多路平衡归并。这里主要介绍的是二路归并。
思想:归并排序使用的是递归的思想,二路归并排序时先将要排序的序列分组,每次将组的长度划分为原来的1/2,当划分到只有一个元素时开始两两一组进行合并。当合并完毕后序列就变的有序。如图
在这里插入图片描述
注意点:当合并数组时需要注意合并序列的下标(索引)并不是从0开始或者到len-1结束

如果文字有点晦涩难懂那么我们可以看一看动画!
在这里插入图片描述
代码实战:

//归并排序用到的合并函数
//在写这个函数的时候一定要注意,开始结束条件不再是0与len-1了,而是从外部传进来的索引
void Merger(int *p,int l,int midde,int r){
    
    
    static int B[maxSize];
    int i=0,j=0,z=0;
    //最后将pB中的元素放进p;
    for(i=l;i<=r;i++){
    
    
        B[i]=p[i];
    }
    for(i=l,j=midde+1,z=l;i<=midde&&j<=r;z++){
    
    
        if(B[i]<B[j]){
    
    
            p[z]=B[i];
            i++;
        }else{
    
    
            p[z]=B[j];
            j++;
        }
    }
    while(j<=r){
    
    
        p[z++]=B[j++];
    }
    while(i<=midde){
    
    
        p[z++]=B[i++];
    }
}
//归并排序
//归并排序思想:
//归并排序有许多种类型,常考的就是路归并,先分组刚开始
//一组一个元素,然后将分的组逐步两两合并,最后得到有序序列
//本函数传进来的是数列的左右下标。
void MergerSort(int *p,int l,int r){
    
    
    if(l<r){
    
    
        // 这个midde始终指向中间位置
        int midde=(l+r)/2;
        MergerSort(p,l,midde);
        MergerSort(p,midde+1,r);
        //合并序列
        Merger(p,l,midde,r);
    }
}

三、堆排序

思想:堆(Heap)是计算机科学中的一种特殊的树状数据结构若满足以下特性,则可称为堆:“给定堆中任意结点P和C,若P是C的父结点,则P的值小于等于(或大于等于)C的值。”若父结点的值恒小于等于子结点的值,则该堆称为最小堆(min heap);反之,若父结点的值恒大于等于子结点的值,则该堆称为最大堆(max heap)。堆中最顶端的那个结点称为根结点(root node),根结点本身没有父结点(parent node)。平时在工作中,我们将最小堆称为小根堆或小顶堆,把最大堆称为大根堆或大顶堆。

在实现该算法的时候我们需要写两个函数,一函数负责生成以传入节点为根节点的堆,一个函数负责抽取堆顶的元素,每次抽取一个,抽取完之后放在当前无序部分的最后一个位置,然后再以树的根节点为基础把树调整为大根堆或者小根堆直到实现每一个数都做过堆的根节点。此时原序列将变的有序。
注意点:在将树调整为堆的时候需要注意,边界条件是严格的,如果不严格将会导致排序过的元素重新回到堆中,将会破坏堆的结构,导致排序出现问题。生成堆的时候边界条件是无序部分的最后一个元素所在的位置。

如果文字有点晦涩难懂那么就看一看动画!
在这里插入图片描述
代码实战:

//堆排序
//排序思想:
//先将数据初始化成为大根堆或者小根堆的形式(大根堆是大的在作为父节点,小根堆是小的作为父节点)
//大根堆用于升序排序,小根堆用于降序
//初始化完毕之后,开始选择元素,每次选出根节点元素换到当前无序部分最后位置,然后重新生成大根堆
//(这里需要注意的是生成大根堆之后选择元素时最大只需要遍历到树的深度即可)

//DeliverBigHeap函数是调整大根堆的函数(每次只调节一颗树)k为当前根节点元素所在的下标(maxIndex是无序部分长度)
//这里的maxIndex限制条件是严格的(如果在判断的时候小了或者大了,均会造成数据混乱)
//所以记住,当选出一个元素的之后,需要将这个元素移到有序段开头,并及时把无序的部分长度减1.
void DeliverBigHeap(int *p,int k,int maxIndex){
    
    
    int sonIndex=k*2+1;
    //调整堆
    while (sonIndex<=maxIndex){
    
    
    	//先比较两个子节点哪个大,把大的拿上来!
        if(sonIndex+1<=maxIndex&&p[sonIndex]<p[sonIndex+1]){
    
    
            sonIndex++;
        }
        //父子换位置
        if(p[k]<p[sonIndex]){
    
    
            int temp=p[k];
            p[k]=p[sonIndex];
            p[sonIndex]=temp;
            // 交换之后继续探测上一个根节点
            k=sonIndex;
            sonIndex=k*2+1;
        }
        //如果一样大或者父节点大,那么该子树已经符合堆定义,直接跳出!
        else{
    
    
            break;
        }
    }
}

void HeapSort(int *p,int len){
    
    
    int temp;
    for(int i=(len-1)/2;i>=0;i--){
    
    
        DeliverBigHeap(p,i,len-1);
    }
//    temp=p[len-1];
//    p[len-1]=p[0];
//    p[0]=temp;
//    printMyarray(p);


//---------------------------------写法1
//    int i;
//    for(i=len-1;i>0;i--){
    
    
//        temp=p[i];
//        p[i]=p[0];
//        p[0]=temp;
//        //
//        DeliverBigHeap(p,0,i-1);
//    }
//    temp=p[i];
//    p[i]=p[0];
//    p[0]=temp;
//----------------------------------写法2
    temp=p[len-1];
    p[len-1]=p[0];
    p[0]=temp;
    for(int i=len-2;i>0;i--){
    
    
        DeliverBigHeap(p,0,i);
        temp=p[i];
        p[i]=p[0];
        p[0]=temp;
    }
//----------------------------------错误写法
//错误原因:因为在调整大根堆的时候实际边界是i-1,这里写成i了
//这在一定情况下容易造成已经排好序的元素再回到树中,然后占据不该占据的位置。
//    for(int i=len-2;i>0;i--){
    
    
//        temp=p[i];
//        p[i]=p[0];
//        p[0]=temp;
//        DeliverBigHeap(p,0,i);
//    }
//    temp=p[len-1];
//    p[len-1]=p[0];
//    p[0]=temp;
}

哇哇哇,排序算法,惨痛的教训,虽然之前感觉学的不错,但在时间的催化下啥也不是了!动图有点快,但是完整的过程又超过了5M,看完整动画传送门(堆排序)其余十大排序

扫描二维码关注公众号,回复: 15644389 查看本文章

猜你喜欢

转载自blog.csdn.net/apple_51931783/article/details/129219247