几种经典排序算法

news2024/11/24 12:21:41

几种经典排序算法

  • 插入排序
    • 折半插入排序法
  • 选择排序
  • 冒泡排序
  • 希尔排序
  • 堆排序
  • 二路归并排序
  • 快速排序

在介绍排序之前,先来说说,研究不同的排序主要是要研究他们的哪些不同:

  1. 时间性能。即排序过程中元素之间的比较次数与元素移动次数。我们此次讨论各种排序方法的时间复杂度时,主要按照最差情况下所需要的比较次数来进行。
  2. 空间性能。除了存放参加排序的元素之外,排序过程中所需要的其他辅助空间。
  3. 排序稳定性。对于值相同的两个元素,排序前后的先后次序不变,则称这种排序方法稳定,否则称其不稳定。

然后还有一个概念我们后面会经常提到:。排序需要若干遍循环完成,每循环一遍,就叫一趟。

然后,我们今天的例子中,都是以从小到大排序为例的。

另外,因为排序免不了需要交换位置,因此我们先写好一个Swap函数用于交换:

void Swap(int* pa,int* pb)
{
int tmp=*pa;
*pa=*pb;
*pb=tmp;
}

插入排序

动画演示
在这里插入图片描述
核心思想:
在第i趟排序中,将序列的第i+1个元素,按照值大小,插入到前i个已排好序的序列中。

静态演示:
将【49,38,97,76,65,13,27,50】进行插入排序:
其中红色部分表示已经排好序的部分,花圈元素表示下一趟要排序的元素。
在这里插入图片描述可以看出来,n个元素,需要n-1趟插入排序才能完全排好。

算法
我们先写一趟排序的算法,然后再加循环。加循环无外就是判断一下起始和结束条件。

void insertSort(int k[],int n)//传入要进行排序的数组以及元素数
{
    int i,j;//i表示第i趟,也表示当前要调整的元素的下标
    //j用来遍历已排好序的序列,用于和第i个元素比较
    int tmp=k[i];
    for(i=1;i<n;i++)
    {
        for(j=i-1;j>=0&&k[j]>tmp;j--)
            k[j+1]=k[j];
        //以上两步用于把比当前元素大的往后调
        //一旦检测到已排序好序列中有比当前元素小的,则把当前元素插到该元素后面
        k[j+1]=tmp;    
    }
   
}

我们用【49,38,97,76,65,13,27,50】这个数组检测一下效果:
在这里插入图片描述
没毛病,跟我们刚刚手动计算的一样。

排序的时间效率
时间效率主要和排序过程中元素之间的比较次数直接有关。

  1. 原始序列按值递增:那么每一趟排序,第一次比较,也就是和当前需要调整元素的前一个元素比较时,就跳出循环了,因为比当前需要调整元素小。因此每个元素进行调整时,都只需要比较1次,那么总共有n-1个元素需要调整,就一共比较n-1次。
  2. 原始序列按值递减:第i趟排序需要和前i个元素都比较一次,因此,整个n-1趟排序需要比较(1+n-1)*(n-1)/ 2=n(n-1)/ 2次

如果以最坏的情况,也就是原始序列递减情况,则插入排序算法的时间复杂度为O(n²)。

由于我们只把比当前元素的元素向后调,相等的元素不调,因此调整之后,值相等的两个元素的相对位置在排序前后并未改变,则插入排序算法是稳定的。

由于没有开辟新的空间,因此插入排序算法的空复杂度为O(1)。

折半插入排序法

这个算法的整体思想和插入法一样,只是在找插入位置的时候,用了折半查找的方法,基于前i-1个元素是有序的。
找到位置的标志,也就是某一趟排序中循环停止的标志为high<low,插入位置即为low。

void BinInsertSort(int k[],int n)
{
    int i,j;
    int high,low,mid;
    for(i=1;i<n;i++)
    {
        int tmp=k[i];
        high=i-1;
        low=0;
        while(high>=low)
        {
            mid=(high+low)/2;
            if(tmp>=k[mid])//将相等情况也纳入,保证了稳定性 
                low=mid+1;
            else
                high=mid-1;
        }
        for(j=i;j>low;j--)
            k[j]=k[j-1];
        k[low]=tmp;
    }
}

选择排序

动画演示
在这里插入图片描述

核心思想
第i趟排序,从序列的后n-i+1个元素中选择一个值最小的元素,将其置于该n-i+1个元素的最前面(与第i个元素交换)

静态演示
红框框起来的表示这一趟中判断出的最小元素,红色部分表示已经排好序的部分。
在这里插入图片描述共有8个元素,但只排7次。和插入排序一样。

void selectsort(int k[],int n)
{
    int i,j,d;//d用于记录最小元素的下标
    int temp;
    for(i=0;i<n-1;i++)
    {
        d=i;
        for(j=i+1;j<n;j++)
        {
            if(k[j]<k[d])
                d=j;
        }
        Swap(&k[d],&k[i]);
    }
}

方法2

void selectsort2(int k[],int n)
{
    int i,j,tmp;
    for(i=0;i<n;i++)
       for(j=i+1;j<n;j++)
           if(k[j]<k[i])
               Swap(&k[j],&k[i]);
}

冒泡排序

动画演示
在这里插入图片描述
核心思想:第i趟中,对序列的**前n-i+1个元素,从第一个元素开始,相邻的两个元素比较大小,若前者大于后者,则交换。

静态演示
在这里插入图片描述

我们发现,选择和插入排序都进行了n-1次,但是为什么冒泡排序小于n-1呢?前两者循环结束的条件都是i,j和n比较,但是冒泡排序,我们需要定义一个变量flag,用于记录,在某趟排序中,是否进行了元素交换。如果进行了,哪怕只有一次,那下一趟循环继续;如果一次也没有,就说明此时序列已经有序,不需要再进行下一趟了。

void bubblesort(int k[],int n)
{
    int i,j,flag=1;
    for(i=n-1;i>0&&flag==1;i--)
    {
        flag=0;
        for(j=0;j<i;j++)
        {
            if(k[j]>k[j+1])
            {
                Swap(&k[j],&k[j+1]);
                flag=1;
            }     
        }
    } 
}

效率分析
冒泡排序算法的排序趟数和原始序列的排序有关,因此排序趟数为【1,n-1】。
排序趟数最少为1次,即原始序列本身就有序。
最多为n-1次,原始序列为逆序。

因此,冒泡排序的时间复杂度,在最少次数下为O(n),即只进行一轮;在最多次数下为O(n²)

冒泡排序过程中,如果两个数相等,则不进行交换,因此是稳定的。

希尔排序

把整个序列分成若干子序列,每个子序列内部先排序。
在这里插入图片描述在每一趟排序结果均是按照上一趟分好的组(颜色一眼的)进行组内排序的结果(组内排序可以用各种排序方法,插入、冒泡都行)。
每组元素的间隔数满足:
在这里插入图片描述
即每一次的gap都是上一次的一半。gap等于1时,整个数组为一个组进行排序,也就是最后一次排序。

每进行一趟排序,数组都会更加接近有序。

我们这里先展示一种组内排序为冒泡排序的希尔排序方法:

void shellsort1(int k[],int n)
{
    int i,j,flag,gap=n;
    //flag用于检测每一趟冒泡排序是否有进行交换,如果没有进行交换,就说明
    //数组已经有序了,不用继续进行排序了
    int temp;
    while(gap>1)
    {
        gap=gap/2;
        do
        {
            flag=0;
            for(i=0;i<n-gap;i++)
            {
                j=i+gap;
                if(k[j]<k[i])
                    Swap(&k[i],&k[j]);
            }     
        } while (flag==1);   
    }
}

再来展示一种内部排序为插入排序的希尔排序:

void shellsort2(int k[],int n)
{
    int i,j,temp,gap=n;
    while(gap>1)
    {
        gap=gap/2;
        for(i=gap;i<n;i++)
        {
            temp=k[i];
            for(j=i;j>=gap&&k[j-gap]>temp;j-=gap)
                k[j]=k[j-gap];
            k[j]=temp;
        }
    }
}

效率分析
举个反例就发现,希尔排序是不稳定的了:
【2a,3,4,1,2b,5,6,8】我们让gap=3,则【2a,1,6】是一组,【3,2b,8】是一组,【4,5】一组。
那么第一趟排序的结果就是:
【1,2b,4,2a,3,5,6,8】我们发现,两个2的相对位置发生了变化,因此不稳定。

希尔排序的时间复杂度比较难搞,我们认为在O(nlogn)和O(n²)之间。

堆排序

之前写过一篇专门讲堆排序的文章:link,但是在本次排序专题里,再把它写一次。

动画演示
在这里插入图片描述
核心思想:先对原始数组进行向下调整,从第一个非叶子结点开始进行。待所有结点都调整完毕之后,形成了一个大堆,然后,依次将堆顶和当前堆的最后个元素交换位置,换完之后进行向下调整

void AjustDown(int k[],int n,int parent)
{
    int child=2*parent+1;
    while(child<n)
    {
        if(child+1<n&&k[child+1]>k[child])
            ++child;
        if(k[child]>k[parent])
        {
            Swap(&k[child],&k[parent]);
            parent=child;
            child=child*2+1;
        }
        else
            break;
    }
}
void heapsort(int k[],int n)
{
    for(int i=(n-1-1)/2;i>=0;i--)
    {
        AjustDown(k,n,i);
    }
    //建好堆了,下面开始排序
    int end=n-1;
    while(end>0)
    {
        Swap(&k[end],&k[0]);
        AjustDown(k,end,0);
        end--;
    }
}

效率分析
时间主要消耗在向下调整了,那么每个元素,都要经过向下调整,调整的次数最多为高度次,即logn,有n个元素,则时间复杂度为O(nlogn)。

堆排序显然也不稳定,调整算法压根没有考虑过数组之间的关系,只考虑了堆的结构。

二路归并排序

动画演示
在这里插入图片描述核心思想
第i趟排序将序列的n/2^ (i-1) 个长度为 2^ (i-1) 的有序子序列依次两两合并为n/2^ i 个长度为2^i的有序子序列。

静态演示
在这里插入图片描述
如果n不等于2^ k,则:
在这里插入图片描述
最后一趟在不等长的两个序列中进行就可以。

大家看到这个基本思路,就知道它大概率是要用到递归的思路的,这种一环套一环,每一环的都基于上一环的结果。

从动态图中我们也能看出来,归并排序是需要一个临时数组的,辅助我们为子序列排序。

void _mergesort(int k[],int begin,int end,int* tmp)
{
    if(begin==end)
        return;
    
    int mid=(begin+end)/2;
    //[begin,mid][mid+1,end]
    _mergesort(k,begin,mid,tmp);
    _mergesort(k,mid+1,end,tmp);
    //左右都排好以后,我们来合并:
    int begin1=begin,end1=mid;
    int begin2=mid+1,end2=end;
    int i=begin1;
    while(begin1<=end1&&begin2<=end)
    {
        if(k[begin1]<k[begin2])
            tmp[i++]=k[begin1++];
        else
            tmp[i++]=k[begin2++];
    }
    while(begin1<=end1)
        tmp[i++]=k[begin1++];
    while(begin2<=end2)
        tmp[i++]=k[begin2++];

    memcpy(k+begin,tmp+begin,sizeof(int)*(end-begin+1));
}
void mergesort(int k[],int n)
{
    int* tmp=(int*)malloc(sizeof(int)*n);
    _mergesort(k,0,n-1,tmp);

    free(tmp);
    tmp=NULL;
}

效率分析
当子序列个数为1(n不是2的次幂时为2个),即n/2^ i 为1的时候,排序完成,即排了logn趟。子序列内部排序的时间复杂度主要来源于归并部分的while循环,时间复杂度为O(n),因此,归并排序的时间复杂度为O(nlogn)

由于开辟了tmp数组,因此空间复杂度时O(n)。

是稳定的。

快速排序

动画演示
在这里插入图片描述为什么右边的先走呢?因为右边的先走可以保证L和R相遇的位置处元素,一定小于key。

void quicksort(int k[],int left,int right)
{
    if(left>=right)
        return;
    int begin=left,end=right;
    int keyi=left;

    while(left<right)
    {
        //right先走,找比key小的
        while(left<right&&k[keyi]<=k[right])//为了防止整个序列里面keyi的右边全部比它大,加一个left<right,以防right一股脑冲出数组头
            right--;
        //left再走,找比key大的
        while(left>right&&k[keyi]>=k[left])
            left++;
        
        Swap(&k[left],&k[right]);
    }
    Swap(&k[left],&k[keyi]);
    keyi=left;
    //[begin,keyi-1][keyi+1,end]
    quicksort(k,begin,keyi-1);
    quicksort(k,keyi+1,end);
}

效率分析
在这里插入图片描述

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

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

相关文章

【JavaEE进阶】——利用框架完成功能全面的图书管理系统

目录 &#x1f6a9;项目所需要的技术栈 &#x1f6a9;项目准备工作 &#x1f388;环境准备 &#x1f388;数据库准备 &#x1f6a9;前后端交互分析 &#x1f388;登录 &#x1f4dd;前后端交互 &#x1f4dd;实现服务器代码 &#x1f4dd;测试前后端代码是否正确 &am…

Caffe、PyTorch、Scikit-learn、Spark MLlib 和 TensorFlowOnSpark 概述

在 AI 框架方面,有几种工具可用于图像分类、视觉和语音等任务。有些很受欢迎,如 PyTorch 和 Caffe,而另一些则更受限制。以下是四种流行的 AI 工具的亮点。 Caffee Caffee是贾扬青在加州大学伯克利分校(UC Berkeley)时开发的深度学习框架。该工具可用于图像分类、语音和…

Nativefier—使用—快速将网站打包成桌面程序

--天蝎座 Nativefier简介 Nativefier是一个命令行工具&#xff0c;仅仅通过一行代码就可以轻松地为任何的网站创建桌面应用程序&#xff0c;应用程序通过Electron打包成系统可执行文件&#xff08;如.app, .exe等&#xff09;&#xff0c;可以运行在Windows&#xff0c;Mac和L…

需求:如何给文件添加水印

今天给大家介绍一个简单易用的水印添加框架&#xff0c;框架抽象了各个文件类型的对于水印添加的方法。仅使用几行代码即可为不同类型的文件添加相同样式的水印。 如果你有给PDF、图片添加水印的需求&#xff0c;EasyWatermark是一个很好的选择&#xff0c;主要功能就是传入一…

Mybatis工作流程和插件开发

在了解插件开发之前&#xff0c;我们先总体的来梳理一下Mybatis的大致执行流程&#xff1a; 1.new SqlSessionFactoryBuilder().build(inputStream):先根据配置文件&#xff08;包含了全局配置文件和映射配置文件&#xff09;初始化一个对象Configuration&#xff08;这里对象里…

LaTex入门教程

目录 1.说明 2.页面的分区 3.入门介绍 &#xff08;1&#xff09;命令 &#xff08;2&#xff09;环境 &#xff08;3&#xff09;声明 &#xff08;4&#xff09;注释 4.代码结构 &#xff08;1&#xff09;导言区 &#xff08;2&#xff09;支持中文 &#xff08;3…

2024都市解压爆笑喜剧《脑洞大开》6月28日上映

随着暑期档的临近&#xff0c;电影市场迎来了一剂强心针——由何欢、王迅、克拉拉、卜钰、孙越、九孔等众多实力派笑星联袂主演的都市解压爆笑喜剧《脑洞大开》正式宣布定档&#xff0c;将于6月28日在全国各大影院欢乐上映&#xff0c;誓为观众带来今夏最畅快淋漓的笑声风暴。 …

逆天改命 17岁中专女生横扫全球数学竞赛

“逆天改命!17岁中专女生横扫全球数学竞赛,清华北大高手纷纷落马!” 最近全网被这则消息震惊了。 来!随便挑几个题目,让大家体验一下阿里巴巴全球数学竞赛的难度? 数学是人工智能算法的基石。它为算法提供了逻辑框架和分析工具,使得人工智能能够处理复杂的数据和问…

驾考模拟 | 电脑上使用浏览器模拟科目一考试

驾考模拟 背景 有个亲戚要考科目一&#xff0c;大叔之前没怎么用过电脑&#xff0c;想要在电脑上练习科目一&#xff0c;找找使用电脑考试的感觉。 有一些本地安装的软件可以满足这个需求&#xff0c;但通常要付费&#xff0c;没这个必要&#xff0c;毕竟只是用来模拟考的。 …

【最新鸿蒙应用开发】——鸿蒙中的“Slot插槽”?@BuilderParam

构建函数-BuilderParam 传递 UI 1. 引言 BuilderParam 该装饰器用于声明任意UI描述的一个元素&#xff0c;类似slot占位符。 简而言之&#xff1a;就是自定义组件允许外部传递 UI Entry Component struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来…

《大数据分析》期末考试整理

一、单项选择题&#xff08;1*9&#xff09; 1.大数据发展历程&#xff1a;出现阶段、热门阶段和应用阶段 P2 2.大数据影响 P3 1&#xff09;大数据对科学活动的影响 2&#xff09;大数据对思维方式的影响 3&#xff09;大数据对社会发展的影响 4&#xff09;大数…

昂科烧录器支持Prolific旺玖科技的电力监控芯片PL7413C1FIG

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Prolific旺玖科技的高度集成的电力监控芯片PL7413C1FIG已经被昂科的通用烧录平台AP8000所支持。 PL7413C1FIG是一款高度集成的电力监控芯片&#xff0c;用于测量电力使用情况的…

vue-饼形图-详细

显示效果 代码 <template> <div style"height: 350px;"> <div :class"className" :style"{height:height,width:width}"></div> </div> </template> <script> import * as echarts from echarts; req…

Typora实现设置代码块默认语言_亲测有效(AutoHotKey方式和修改配置文件)

Typora实现设置代码块默认语言&#xff08;AutoHotKey方式和修改配置文件&#xff09; 前言&#xff0c;需求使用AutoHotKey热键脚本【最简单方便】实现步骤建议 最终效果其他方法自定义Typora代码块快捷键设置。应对ctrlshiftk快捷键被其他占用的情况。 前言&#xff0c;需求 …

07--Zabbix监控告警

前言&#xff1a;和普米一样运维必会的技能&#xff0c;这里总结一下&#xff0c;适用范围非常广泛&#xff0c;有图形化界面&#xff0c;能帮助运维极快确定问题所在&#xff0c;这里记录下概念和基础操作。 1、zabbix简介 Zabbix是一个基于 Web 界面的企业级开源解决方案&a…

厂里资讯之自媒体文章自动审核

自媒体文章-自动审核 1)自媒体文章自动审核流程 1 自媒体端发布文章后&#xff0c;开始审核文章 2 审核的主要是审核文章的内容&#xff08;文本内容和图片&#xff09; 3 借助第三方提供的接口审核文本 4 借助第三方提供的接口审核图片&#xff0c;由于图片存储到minIO中&…

高速信号——NRZ,PAM4调制技术

1&#xff1a;码元 了解调制技术需要引出“码元”的概念。 一个码元就是一个脉冲信号&#xff0c;即一个最小信号周期内的信号&#xff0c;我们都能够理解&#xff0c;最简单的电路&#xff0c;以高电平代表1&#xff0c;低电平代表0&#xff0c;一个代表1或者0的信号&#x…

Linux基础I/O之文件描述符fd 重定向(上)

目录 一、预备知识 二、C语言中的文件接口 三、系统调用中的文件接口 一、预备知识 首先我们要明确的一个观点是 --- 文件 内容 属性。而且我们之前也还将过一个概念&#xff0c;那就是Linux下一切皆文件。 内容是数据&#xff0c;属性也是数据 --- 那么也就是说我…

t265 jetpack 6 px4 ros2

Ubuntu22.04 realsenseSDK2和ROS2Wrapper安装方法,包含T265版本踩坑问题_ros2 realsense-CSDN博客 210 git clone https://github.com/IntelRealSense/librealsense.git 212 git branch 215 git tag 218 git checkout v2.51.1 219 git branch 265 git clone https://…

C语言---------深入理解指针

目录 一、字符指针 二、指针数组&#xff1a; 三、数组指针&#xff1a; 1、定义&#xff1a; 2、&数组名和数组名区别&#xff1a; 3、数组指针的使用&#xff1a; 四、数组参数&#xff0c;指针参数&#xff1a; 1、一维数组传参&#xff1a; 2、二维数组传参&am…