数据结构——C语言实现常见排序(插入排序、希尔排序、选择排序、堆排序、冒泡排序)

news2024/11/16 21:48:40

引言:

现在是北京时间2023年6月23日13点19分,度过了一个非常愉快的端午节。由于刚从学校回家,一下子伙食强度直升了个两三个档次。这也导致我的肠胃不堪重负,我也准备等会去健身房消耗一下盈余的热量。回到家陪伴爷爷走人生最后的阶段才是我这个暑假最重要的事情。自从爷爷病重后,起居都需要家人照顾,我不仅感慨岁月夺人呐。兴许五六十年后,子孙也能够在我人生最后的阶段陪伴我吧。
在这里插入图片描述

排序的概念

所谓排序,就是使一组数据,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。在日常生活中处处都有排序,比如学校的考试中会有对成绩进行排序、当我们购物时会有对销量或价格等进行排序。
合理对排序稳定性做一下介绍,假设在待排序的数据中,存在多个具有相同的关键字的数据,若经过排序,这些记录的相对顺序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。

插入排序

思想

插入排序的思想其实类似我们玩扑克牌时,每次抓牌都会进行一次排序。这里就按升序来说,每一次插入排序都是将要排序的数据和它前面的数据作比较,当需要排序的数据比前面小就交换,并将它向前移,比它大的数据往后挪,直到前面的数据比它小或者排序的数据下标已经小于0就停止单趟排序。
在这里插入图片描述

单趟排序代码实现

根据插入排序的思想,我们从1下标位置开始插入数据进行排序,所以当前一个数据比后一个数据大的时候,我们就进行交换。直到插入的位置小于数组的有效范围就停止单趟排序。这样小的数就会往前移动,大的数整体往后挪动。

        int end ;//用于表示当前数据的下标
        int tmp ;//插入数据的值
        while(end >= 0)
        {
        //前一个数比它小就覆盖
            if(tmp < arr[end])
            {
                arr[end+1] = arr[end];
                end--;
            }
         //否则就结束单趟排序
            else
            {
                break;
            }
        }

完整代码实现

有了单趟排序的实现,我们根据整体的思想就可以写出外循环。即外循环从1开始到n-1结束。当单趟排序结束时,需要对超出数组范围的数重新写回数组内。例如当end == -1 时就要将它写会0下标处。

//插入排序——升序
void InsertSort(int* arr, int n)
{
    for(int i = 1; i < n;i++)
    {
        int end = i - 1 ;
        int tmp = arr[i];
        while(end >= 0)
        {
            if(tmp < arr[end])
            {
                arr[end+1] = arr[end];
                end--;
            }
            else
            {
                break;
            }
        }
        arr[end+1] = tmp;
    }
}

特性总结

一、当数据越接近有序插入排序的效率越高,排升序时,当数据为升序(最好情况),插入排序的时间复杂度为O(N)。因为最好情况只需要遍历n-1次数据并进行判断即可。
二、最坏情况下,插入排序的时间复杂度为O(N^2)。即排升序时,当数据为降序。因为每次单趟排序挪动数据的时间复杂度为O(N),整体要走N-1趟排序,所以时间复杂度为O(N ^ 2)。
三、插入排序的时间复杂度为O(N^2),空间复杂度为O(1)。
四、插入排序是一种稳定的排序。排序稳定性的定义为排序前后数组内相同元素的相对位置不变。

希尔排序

思想

希尔排序也称缩小增量排序,思想为取一个整数位分组的标识,这里我就用gap来表示这个值,将待排的数据按照gap步的差距分成多组。并对这个多组数据进行直接插入排序以让它们越来越接近有序,一开始gap值比较大,大的数就会快速往后挪,小的数就会被往前推。随着gap逐渐变小,数据越来越接近有序,最后当gap=1时,数据已经接近有序,此时就会进行直接插入排序。
在这里插入图片描述

在这里插入图片描述

单趟排序代码实现

单趟排序的实现思路同插入排序,不同的是,单趟排序这里因为要分组,所以是以组间隔gap来分组进行单趟排序,最后一趟当gap==1时,就是插入排序了。

	int end;
	int tmp;
	while(end >= 0)
	{
	    if(tmp < arr[end])
	    {
	        arr[end+gap] = arr[end];
	        end-= gap;
	    }
	    else
	    {
	        break;
	    }
	}

代码实现

这里gap的取法有很多种,大部分都是/2或者/3+1取gap值。我这里就以/2为例写。外循环的控制的逻辑大体来说就是控制gap使区间局部有序,最后当gap==1时,直接在接近有序的数组上进行插入排序。下面代码可以参考,具体的写法看个人喜好,思想上基本都是大差不差的。

//希尔排序——升序
void ShellSort(int* arr, int n)
{
    int gap = n;
    while(gap > 1)
    {
        gap/=2;
        for(int i = 0; i < n-gap;i++)
        {
            int end = i ;
            int tmp = arr[i+gap];
            while(end >= 0)
            {
                if(tmp < arr[end])
                {
                    arr[end+gap] = arr[end];
                    end-= gap;
                }
                else
                {
                    break;
                }
            }
            arr[end+gap] = tmp;
        }
    }
}

特性总结

一、希尔排序其实就是对直接插入排序的优化。通过根据gap分组先预排序极大程度的优化了直接插入排序最坏情况时的窘迫状况,可以快速让大的数后挪,小的数前移。
二、当gap大于1时,都是预排序,目的是让数组更加接近有序,优化直接插入排序的次数。这对于直接插入排序思想是个极大程度的优化,效率较高。
三、由于gap取值方面并没有一个比较官方统一的数值,但是必须保证gap最后一次必须是1。所以时间复杂度方面并没有办法进行一个标准的定义。这里就引用一下严老师的内容来对此进行一个比较好的解释。
在这里插入图片描述
希尔排序的时间复杂度O(N^1.3),由此也可以把希尔排序纳入O(NLogN)这个时间复杂度量级的排序当中。当N越来越大时,N ^1.3次方比起NLogN还是又不小的差距
在这里插入图片描述
四、希尔排序是不稳定的排序,因为可能在gap分组预排序顺序可能会受到影响。

选择排序

思想

就是每一次遍历整个数组,求出整个数组的最大值/最小值并把它放到合适的位置就完成单趟排序。根据每一次遍历数组都可以求出最大值和最小值,也可以将选择排序优化为一次将最大值和最小值放到合适的位置。不过需要进行特殊判断避免left和max重叠问题,导致交换后,无法让max到合适的位置。然后冲复上述步骤直到区间内只有一个数据,则排序结束。
在这里插入图片描述

单趟排序代码实现

遍历数组,找出最大和最小值并把它们挪到对应的下标位置处。需要注意的是由于同时找大和找小,所以要避免重叠特殊判断,否则会有覆盖数据的情况出现。

 //每一次找出区间内最大和最小的数
	int mini;
	int maxi;
	for(int i = left + 1; i <= right; i++)
	{
	    if(arr[mini] > arr[i])
	        mini = i;
	    if(arr[maxi] < arr[i])
	        maxi = i;
	}
	//由于同时找大和找小,所以要避免重叠特殊判断
	Swap(&arr[left],&arr[mini]);
	if(maxi == left)
	    maxi = mini;
	Swap(&arr[right],&arr[maxi]);

代码实现

整体就是从0到n-1区间逐步往中间缩。当left >= right时,表示没有有效区间了。

//选择排序
void SelectSort(int* arr, int n)
{
    int left = 0;
    int right = n - 1;
    while(left < right)
    {
        //每一次找出区间内最大和最小的数
        int mini = left;
        int maxi = left;
        for(int i = left + 1; i <= right; i++)
        {
            if(arr[mini] > arr[i])
                mini = i;
            if(arr[maxi] < arr[i])
                maxi = i;
        }
        //避免重叠特殊判断
        Swap(&arr[left],&arr[mini]);
        if(maxi == left)
            maxi = mini;
        Swap(&arr[right],&arr[maxi]);
        
        left++;
        right--;
    }
}

特性总结

一、选择排序比较容易理解,但是效率实在太过低效。通常也不会用到它来进行排序。
二、它的时间复杂度一如既往的稳定在O(N^2),空间复杂度为O(1)。
三、它是一个不稳定的排序。举一个样例,如果出现最大值重复的情况,那么本该在前面的最大值会因为先被交换到最后,而导致相同值的顺序错乱。

堆排序

点击这里跳转到堆排序介绍,由于前面已经介绍过了这里就不多做赘述。根据上面介绍的直接选择排序的介绍不难发现,其实堆排序就是对于直接选择排序的一种优化。不过这是一种凭借堆这种完全二叉树的结构来建堆提升选大根/小根的效率来进行排序。

代码实现

void AdjustDown(int* a, int n, int parent)
{
    int child = parent * 2 + 1;
    while (child < n)
    {
        // 选出左右孩子中大的那一个
        if (child + 1 < n && a[child + 1] > a[child])
        {
            ++child;
        }
		//判断父子关系
        if (a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            //迭代
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}


//堆排序
void HeapSort(int* arr, int n)
{
    //向下调整建堆
    for (int i = (n - 1 - 1) / 2; i >= 0; --i)
    {
        AdjustDown(arr, n, i);
    }
    //排序:大数往下沉,然后堆顶向下调整堆
    int end = n - 1;
    while (end > 0)
    {
        Swap(&arr[end], &arr[0]);
        AdjustDown(arr, end, 0);

        --end;
    }
}

特性总结

一、使用堆这个数据结构进行选树,大大提升了效率。
二、堆排序的时间复杂度为O(N*LogN),空间复杂度为O(1)。
三、堆排序是一种不稳定的排序。

冒泡排序

思想

冒泡排序的思想是从第0个位置开始依次和后面的数据进行比较和交换。以升序为例,当前一个数比后一个数大的时候,交换两个数位置,直到单趟结束,最终最大的数会出现在它应该出现的位置。最坏的情况下需要走n-1趟冒泡排序。

单趟排序代码实现

两两比较,直到找到单趟内最大的值,并让它到它合适的位置。

  for (int i = 1; i < n; i++)
        {
            if (arr[i - 1] > arr[i])
            {
                Swap(&arr[i - 1], &arr[i]);
            }
        }

代码实现

//冒泡排序
void BubbleSort(int* arr, int n)
{
    for (int j = 0; j < n - 1; j++)
    {
        //若有序就跳出循环
        //优化后,最好情况时间复杂度为O(N)
        int flag = 0;
        for (int i = 1; i < n-j; i++)
        {
            if (arr[i - 1] > arr[i])
            {
                Swap(&arr[i - 1], &arr[i]);
                flag = 1;
            }
        }
        if (flag == 0)
        {
            break;
        }
    }
}

特性总结

一、冒泡排序是一个就有教学意义的排序算法。
二、冒泡排序的时间复杂度为O(N^2),空间复杂度为O(1)。
三、冒泡排序是一种稳定的排序.

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

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

相关文章

C++11 线程库—线程操作(更新中)

前言 在C11推出线程库前&#xff0c;Windows和Linux操作系统的线程操作并不同&#xff0c;这就导致多线程程序无法跨平台&#xff0c;如果想要跨平台&#xff0c;会很麻烦并且容易出错。C11推出的线程库就解决了这一问题。 因为在Windows和Linux操作系统中有一些独特的常量&am…

OpenGL 鼠标拾取模型

1.简介 在我们的场景中&#xff0c;使用鼠标光标点击或“挑选”一个3d对象是很有用的。一种方法是从鼠标投射3d光线&#xff0c;通过相机&#xff0c;进入场景&#xff0c;然后检查光线是否与任何物体相交。这通常被称为光线投射。 我们不是从局部空间中的网格开始&#xff0c…

gRPC 实践

RPC 包管理&#xff0c;1.12前&#xff1b;旧版本要设置GO111MODULEoff&#xff1b;查找gopath/src;goroot/src&#xff1b;几乎没有包管理&#xff1b; 新版本&#xff1b;go.mod&#xff1b; module xxx go version设置GO111MODULEon 是什么 远程过程调用&#xff1b; …

Windows页面置换算法与文件操作

实验一 一、实验内容或题目&#xff1a; 随机产生页面访问序列&#xff0c;并实现LRU, FIFO, OPT三种算法进行缺页比较 二、实验目的与要求&#xff1a; 1、编写程序&#xff0c;随机产生页面访问序列&#xff0c;并实现LRU, FIFO, OPT三种算法进行缺页比较。 2、理解三种算…

自监督学习简介

1.  自监督学习 自监督学习是可以看做是一种特殊的无监督学习的一个子类别&#xff08;但并非无监督学习&#xff09;&#xff0c;因为它利用了未标记的数据。 关键思想是让模型无需手动标签即可学习数据表示。一旦模型学会了如何表示数据&#xff0c;那么它就可以用较少量的…

liunx+docker+rabbitmq安装延迟队列插件

安装版本 rabbit: RabbitMQ 3.8.16 erlang: Erlang 23.3.2 rabbit: rabbitmq_delayed_message_exchange-3.8.9-0199d11c.ez 准备 1.rabbmitMQ 安装 docker pull rabbitmq 2.rabbmitMQ 启动 docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER用户…

10 种分布式系统必备模式

在当今的技术领域中&#xff0c;分布式系统已成为许多大型应用程序和平台的核心。构建高性能、可伸缩和可靠的分布式系统是一个复杂的挑战&#xff0c;需要合理的架构设计和模式选择。本文将介绍10个必备的分布式系统模式&#xff0c;帮助您更好地理解和应用这些模式以提升系统…

pytorch笔记:RNN 系列

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN RNN可以处理变长序列&#xff0c;是因为其每个时刻的参数是共享的RNN每算出一个时刻都可以输出&#xff0c;适合流式输出&#xff0c;但串行计算比较慢&#xff0c;无法获取太长的历史信息RNN 初始隐状态不提供默认是0&am…

一、枚举类型——用枚举实现状态机

枚举类型很适合用来实现状态机。状态机可以处于有限数量的特定状态。它们通常根据输入&#xff0c;从一个状态移动到下一个状态&#xff0c;但同时也会存在瞬态。当任务执行完毕后&#xff0c;状态机会立即跳出所有状态。 每个状态都有某些可接受的输入&#xff0c;不同的输入…

你应该知道的 Python 自动化脚本

概要 我们都有一些需要重复做的任务。幸运的是&#xff0c;我们可以将其中一些过程自动化&#xff0c;这样我们就可以专注于做其他真正需要精力和注意力的事情。 在这篇文章中&#xff0c;我们将谈论一些 Python 自动化脚本&#xff0c;你可以轻松地用它们来执行自动化任务。重…

巨星内马尔为孕期出轨道歉了!喊话女友:“我不能想象失去你”

近日&#xff0c;巴西球星内马尔在女友布鲁娜孕期出轨的传闻引起了社会广泛关注。 22日凌晨&#xff0c;内马尔在自己的社交媒体上发文回应并道歉&#xff0c;表示自己在球场内外都会犯错&#xff0c;但私生活的问题他会在家里解决。 他还重申了自己已经为犯下的错误和不必要的…

python爬虫_函数的使用

文章目录 ⭐前言⭐python函数&#x1f496; 参数传递—值&#x1f496; 参数传递—引用&#x1f496; 多参数(*)&#x1f496;lambda匿名函数 结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于python函数入门使用。 该系列文章&#xff1a; python爬虫_基本数…

【031】C++类和对象之运算符重载详解和代码实践(最全讲解)

C类和对象之运算符重载详解 引言一、运算符重载的基本概念1.1、可重载的运算符1.2、不可重载的运算符 二、重载 << 运算符&#xff08;全局函数实现&#xff09;三、重载 >> 运算符&#xff08;全局函数实现&#xff09;四、重载 运算符4.1、全局函数实现4.2、成员…

leetcode257. 二叉树的所有路径(java)

二叉树的所有路径 leetcode257. 二叉树的所有路径题目描述DFS深度优先遍历 二叉树专题 leetcode257. 二叉树的所有路径 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/binary-tree-paths 题目描述 给你一个二叉树的根节…

【系统开发】尚硅谷 - 谷粒商城项目笔记(十一):K8S

文章目录 K8s简介架构原理核心概念控制平面组件&#xff08;Control Plane Components&#xff09;kube-apiserveretcdkube-schedulerkube-controller-managercloud-controller-manager Node 组件kubeletkube-proxy容器运行时&#xff08;Container Runtime&#xff09; 插件&a…

Gee 项目复现

序言 复现&#xff1a;原链接 一个Web框架需要支持的功能&#xff0c; 路由&#xff0c;请求到响应函数的映射&#xff0c;支持动态路由如hello/:name,hello/*模板&#xff0c;使用内置模板引擎渲染机制。鉴权&#xff1a;分组插件&#xff1a;中间件 第一天 HTTP基础 启动…

基于边界点优化和多步路径规划的机器人自主探索

论文题目&#xff1a;Autonomous Robotic Exploration Based on Frontier Point Optimization and Multistep Path Planning 中文题目&#xff1a;基于边界点优化和多步路径规划的机器人自主探索 作者&#xff1a;Baofu Fang &#xff1b;Jianfeng Ding ; Zaijun Wang 作者机…

5.5.2 IPv6数据报格式

5.5.2 IPv6数据报格式 首先我们来回忆一下IPv4数据报首部格式&#xff08;5.2.3 IP数据报&#xff08;一&#xff09;IP数据报的格式&#xff09;&#xff0c;包括20个字节的固定部分和长度可变的选项部分&#xff0c;如图 红色方框标注的是在IPv6中会消失的字段&#xff0c;椭…

小白也会的------新建Python虚拟环境,查看该虚拟环境的路径,将该虚拟环境的所有库和版本号导出到一个 requirements.txt 文件中

我的目录标题 1、新建Python虚拟环境2、查看该虚拟环境的路径3、将该虚拟环境的所有库和版本号导出到一个 requirements.txt 文件中4、如果你只需要将当前虚拟环境中安装的所有库和版本号导出到一个 requirements.txt 文件中&#xff0c;而不需要包括每个库的来源&#xff0c;可…

KMP算法基础

前言 KMP算法是我们数据结构串中最难也是最重要的算法。难是因为KMP算法的代码很优美简洁干练&#xff0c;但里面包含着非常深的思维。真正理解代码的人可以说对KMP算法的了解已经相当深入了。而且这个算法的不少东西的确不容易讲懂&#xff0c;很多正规的书本把概念一摆出直接…