C语言-排序

常见的排序算法分为以下四种,插入排序,选择排序,交换排序,归并排序。

一、插入排序

(一)直接插入排序

直接插入排序,将一段数组看做被分成已排序序列和未排序序列,排序过程是从未排序序列的元素开始,该元素与已排序序列的元素从后向前扫描,找到第一个小于(或大于)该元素的已排序项,然后将该未排序项插入到已排序序列中的适当位置。

void InsertSort(int* a, int n)
{//最后一组,[0, n-2]for (int i = 0; i < n - 1; i++)//是n不是n-1!!!{int end = i;int tmp = a[end + 1];//[0,end]是有序的,从end+1开始插入while (end >= 0){if (tmp < a[end])//比tmp值大的,往后挪{a[end + 1] = a[end];end--;}else{//不在里面放是因为,如果要插入的,比所有值都小//end此时为-1,循环结束,跳不到elsebreak;}}a[end + 1] = tmp;}
}

(二)希尔排序

希尔排序是针对直接插入排序的改进

将数组元素分为gap组,即每隔gap个的元素为一组,组内排序,然后修改gap值,当gap为1时,就是直接插入排序

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){//gap = gap / 2;//除几都可以,除3比较好,但是最后不能保证是1gap = gap / 3 + 1;//这样就保证了最后结果一定是1for (size_t i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = tmp;}OP()把打印注释//printf("gap:%2d->", gap);//PrintArray(a, n);}
}

二、选择排序

(一)选择排序

选择排序,遍历整个数组,每次选择出最大的

进行升级,每次遍历找出最小最大的

注意下面代码中的
        if (begin == maxi)
            maxi = mini;

这个是很有必要的

比如说begin开始是最大值,和mini交换之后,接下来最大maxi要和begin进行交换,但是begin此时的值不是最大值

void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i < end; i++)//从begin+1是因为,自己跟自己比没意义,<end也是{if (a[i] > a[maxi])maxi = i;if (a[i] < a[mini])mini = i;}Swap(&a[begin], &a[mini]);if (begin == maxi)maxi = mini;Swap(&a[end], &a[maxi]);++begin;--end;}
}

(二)堆排序

void AdjustDown(int* a, int n, int parent)
{// 先假设左孩子小int child = parent * 2 + 1;while (child < n)  // 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* a, int n)
{// 向下调整建堆 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

三、交换排序

(一)冒泡排序

两层循环,内层单趟是找出最大的

void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){//里面是单趟int flag = 0;for (int j = 0; j < n - 1 - i; j++){if (a[j - 1] > a[j]){Swap(&a[j - 1], &a[j]);flag = 1;}}if (flag == 0)break;}
}

(二)快速排序

1、hora方法

以第一个数为key,将比key小的元素放在基准元素的左边,将比key大的元素放在基准元素的右边。

遍历结束后,key左边所有的都比key小,右边都比key大,key就排序完成了,然后在递归调用快排函数排序左右两侧。

但是这样有个问题,假如数组一开始就有序,那么排序的深度很大(需要递归多次),这就可以使用三数取中来优化代码,left,right,以及(left+right)/2这三个是数的下标,找到中间值给left进行交换。

同时,快排的递归调用类似于满二叉树,后三层递归次数占总次数很大,可以使用小区间优化,当该区间元素个数不够时,使用一种排序方法排序,这里选择直接插入排序,(直接插入排序在小规模数据上表现良好)。

int GetMidi(int* a, int left, int right)
{//取得是大小在中间的值int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right])return midi;else if (a[left] < a[right])//走到这里说明a[left] < a[midi], a[right] < a[midi]return right;elsereturn left;}else//a[left] >= a[midi]{if (a[midi] > a[right])return midi;else if (a[left] < a[right])return left;elsereturn right;}
}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小区间优化,不再递归分割排序,减少递归的次数if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);//a+left是因为InsertSort需要数组起始地址}else{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (begin < end){// 右边找小while (begin < end && a[end] >= a[keyi]){--end;}// 左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}Swap(&a[begin], &a[end]);}//while运行完,左边全是比key小,右边全是比key大Swap(&a[keyi], &a[begin]);keyi = begin;// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

2、前后指针方法

prev和cur

cur先向右移动,找到比key小的,然后++prev,交换prev和cur的值

遍历一遍,prev最后位置左边,都是比key值小的,跟Quick相似,左边全比他小,右边比key大

int PartSort2(int* a, int left, int right)
{三数取中//int midi = GetMidi(a, left, right);//Swap(&a[left], &a[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right)//还在区间{//由于一开始紧挨着,后面判断是为了防止自己交换//顺序也不能反,如果a[cur] > a[keyi],就不会执行后面的,也就是说cur++但是prev不++if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}//不要写else,cur无论什么情况都要++cur++;}Swap(&a[prev], &a[keyi]);return prev;
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小区间优化,不再递归分割排序,减少递归的次数if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);//a+left是因为InsertSort需要数组起始地址}else{int keyi = PartSort2(a,left,right);//修改此处即可// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

3、非递归方法

快排非递归 -- 数据用栈模拟(队列也可以,队列先进先出,数据就不用倒着进)
 用栈类似于DFS(深度优先遍历)
 用队列类似于BFS(广度优先遍历 -- 二叉树的广度就是层序遍历)
递归会建立栈帧,(溢出跟深度有关)
在递归里,栈帧存的核心数据是 -- 区间(所以非递归方式,栈存放区间)
循环每走一次,取栈顶区间,进行单趟排序,右左子区间入栈(右左是因为栈后进先出)
 函数调用是在栈区取空间(栈只有8M)
 数据结构的栈空间不够去扩容是在堆(堆在32位下是2G)

void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st))// 循环每走一次,相当于一次递归{int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort2(a, begin, end);// [begin, keyi - 1] keyi [keyi + 1, end]// 先入右[keyi + 1, end]if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}//再入左[begin, keyi - 1]if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}
}

四、归并排序

假设把数组分成两段,如果左右区间有序,两个指针指向左右区间第一个,一个比另一个小,就比所有的小
把小的尾插到一个tmp数组
类似二叉树的后序,有序就归并,无序就递归

1、递归

void _MergeSort(int* a, int* tmp, int begin, int end)//_从习惯和风格上是子函数
{//只有一个值,返回if (begin >= end)return;int midi = (begin + end) / 2;//[begin , midi] [midi + 1, end] -- 有序就可以进行归并//不能是[begin , midi - 1] [midi, end]!!!//假设begin=0,end=1,此时midi=0,如果-1得到的分组就是[0,-1],[0,1]//[偶数,偶数+1]分组得到,[偶数,偶数],[偶数,偶数+1] -- [偶数,偶数+1]一直出现,造成栈溢出x//子问题递归来实现有序_MergeSort(a, tmp, begin, midi);_MergeSort(a, tmp, midi + 1, end);//归并int begin1 = begin, end1 = midi;int begin2 = midi+1, end2 = end;int i = begin;//有一个满足是结束的条件,while循环里要写继续的条件!!!while (begin1 <= end1 && begin2 <=end2){if (a[begin1] <= a[begin2])tmp[i++] = a[begin1++];elsetmp[i++] = a[begin2++];}//执行完之后,只排序了一个,再把两个里面的都进一遍就行while(begin1 <= end1)tmp[i++] = a[begin1++];while(begin2 <= end2)tmp[i++] = a[begin2++];//数据还在tmp数组中,需要拷贝回去memcpy//不是拷整个区间,是begin到endmemcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail!");return;}_MergeSort(a, tmp, 0, n-1);free(tmp);tmp = NULL;
}

2、非递归

 使用非递归 -- 一开始每两个数归并,然后每四个数归并...把递归倒着看
 gap -- 每组归并数据的数据个数(gap是其中一组归并数据的个数)
 gap, gap 这样归并,所以数据个数*2
 [i, i + gap -1], [gap + i,i + 2*gap - 1]

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap)//i代表每组归并的起始位置{//[begin1,end1][begin2,end2]//end = 起点 + 个数 - 1int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap * 2 - 1;printf("[%d, %d] [%d, %d]", begin1, end1, begin2, end2);//第二组都越界 -- [begin1,end1] n [begin2,end2]if (begin2 >= n){break;}//第二组begin2没越界,end2越界,需要修正一下继续归并 -- [begin1,end1][begin2, (n) end2]if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])//归并排序,这里加个=才是稳定的,当相等的时候取前一个tmp[j++] = a[begin1++];elsetmp[j++] = a[begin2++];}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));}gap *= 2;printf("\n");}free(tmp);tmp = NULL;
}

五、各个排序的时间复杂度及稳定性

稳定性指的是相同的值排完后,相对顺序不变

直接插入排序

两层循环

时间复杂度O(N^2),每次插入大约移动一半元素

最坏时间复杂度O(N^2),数组完全逆序

稳定,因为插入操作不会改变相同元素的相对顺序

希尔排序

时间复杂度O(N^1.3) - O(N^2),O(N^1.3)即可

最坏时间复杂度O(N^2),取决于分组情况gap

不稳定,元素的移动可能跨越多个间隔,导致相同元素相对顺序改变

选择排序

时间复杂度O(N^2),每次插入大约移动一半元素

最坏时间复杂度O(N^2),数组完全逆序

不稳定,每次选择最小元素并交换,会改变相同元素的相对顺序

堆排序

时间复杂度O(nlogn),

最坏时间复杂度O(nlogn),

由于堆的调整操作,无论输入数组的初始状态如何,时间复杂度不变

不稳定,在调整堆的过程中,会交换元素,可能改变相同元素的相对顺序

冒泡排序

时间复杂度O(N^2),

最坏时间复杂度O(N^2),当数组完全逆序时

稳定,相邻元素比较和交换,相同元素不会交换位置

快速排序

时间复杂度O(nlogn),

最坏时间复杂度O(N^2),当每次选择的基准元素都为当前子数组中的最大或最小元素时,导致划分极不均衡,递归深度达到最大

不稳定,分区过程中,基准元素的交换可能导致相同元素相对顺序改变

归并排序

时间复杂度O(nlogn),

最坏时间复杂度O(nlogn),

无论数组初始状态如何,通过不断将数组对半划分并合并,所以其时间复杂度不会变

稳定,在合并过程中,可以保证相同元素的相对顺序不变

    while (begin1 <= end1 && begin2 <=end2)
    {
        if (a[begin1] <= a[begin2])
            tmp[i++] = a[begin1++];
        else
            tmp[i++] = a[begin2++];
    }

if中的<=,=可以确保相同值时相对顺序不变(递归非递归都是这个=)

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

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

相关文章

【Java笔记】LinkedList 底层结构

一、LinkedList 的全面说明 LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复)&#xff0c;包括null线程不安全&#xff0c;没有实现同步 二、LinkedList 的底层操作机制 三、LinkedList的增删改查案例 public class LinkedListCRUD { public stati…

网管平台(基础篇):路由器的介绍与管理

路由器简介 路由器&#xff08;Router&#xff09;是一种计算机网络设备&#xff0c;它的主要作用是将数据通过打包&#xff0c;并按照一定的路径选择算法&#xff0c;将网络传送至目的地。路由器能够连接两个或更多个网络&#xff0c;并根据信道的情况自动选择和设定路由&…

排序算法(2):选择排序

问题 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 选择排序 选择排序每次从待排序序列中选出最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放到序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小&#xff08;或最大&#xff09;元素…

Tongweb8命令行使用收集(by lqw)

文章目录 声明对应版本修改thanos用户密码部署应用到默认实例节点相关操作新增节点(一般一个服务器ip只能装一个节点)启动节点(需确认节点没有运行)停止节点删除节点节点新增应用节点查看应用节点启动应用节点停止应用节点卸载应用(谨慎操作,卸载后应用就没有了,建议备份后…

Artec Leo3D扫描仪在重型机械设备定制中的应用【沪敖3D】

挑战&#xff1a;一家加拿大制造商需要有效的方法&#xff0c;为富于变化且难度较高的逆向工程&#xff0c;快速、安全、准确地完成重型机械几何采集。 解决方案&#xff1a;Artec Leo, Artec Studio, Geomagic for SOLIDWORKS 效果&#xff1a;Artec Leo三维扫描代替过去的手动…

Nginx 限制只能白名单 uri 请求的配置

实际生产项目中&#xff0c;大多数时候我们会将后端的 http 接口通过前置 nginx 进行反向代理&#xff0c;对互联网用户提供服务。往往我们后端服务所能提供的接口服务是大于互联网用户侧的实际请求的接口地址数量的&#xff08;例如后端服务一共有100个api接口&#xff0c;经过…

题海拾贝:力扣 141.环形链表

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

Vite快速构建Vue教程

步骤 1: 初始化项目目录 创建一个名为 projects 的文件夹&#xff0c;作为存放所有 Vite 项目的根目录。这个文件夹将容纳多个独立的 Vite 项目。 步骤 2: 创建 Vite 项目 右键点击 projects 文件夹并选择“在此处打开终端”或使用您偏好的代码编辑器&#xff08;如 VSCode&…

深入理解 CSS 文本换行: overflow-wrap 和 word-break

前言 正常情况下&#xff0c;在固定宽度的盒子中的中文会自动换行。但是&#xff0c;当遇到非常长的英文单词或者很长的 URL 时&#xff0c;文本可能就不会自动换行&#xff0c;而会溢出所在容器。幸运的是&#xff0c;CSS 为我们提供了一些和文本换行相关的属性&#xff1b;今…

【NumPy进阶】:内存视图、性能优化与高级线性代数

目录 1. 深入理解 NumPy 的内存视图与拷贝1.1 内存视图&#xff08;View&#xff09;1.1.1 创建视图1.1.2 视图的特点 1.2 数组拷贝&#xff08;Copy&#xff09;1.2.1 创建拷贝1.2.2 拷贝的特点 1.3 视图与拷贝的选择 2. NumPy 的优化与性能提升技巧2.1 向量化操作示例&#x…

HarmonyOS 5.0应用开发——属性动画

【高心星出品】 文章目录 属性动画animateTo属性动画animation属性动画 属性动画 属性接口&#xff08;以下简称属性&#xff09;包含尺寸属性、布局属性、位置属性等多种类型&#xff0c;用于控制组件的行为。针对当前界面上的组件&#xff0c;其部分属性&#xff08;如位置属…

机器学习支持向量机(SVM)算法

一、引言 在当今数据驱动的时代&#xff0c;机器学习算法在各个领域发挥着至关重要的作用。支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;作为一种强大的监督学习算法&#xff0c;以其在分类和回归任务中的卓越性能而备受瞩目。SVM 具有良好的泛化…

介绍一款docker ui 管理工具

http://vm01:18999/main.html 管理员登陆账号 jinghan/123456 ui启动命令所在文件夹目录 /work/docker/docker-ui 参考链接 DockerUI&#xff1a;一款功能强大的中文Docker可视化管理工具_docker ui-CSDN博客

Motrix WebExtension 使用教程

Motrix WebExtension 使用教程 项目地址:https://gitcode.com/gh_mirrors/mo/motrix-webextension 项目介绍 Motrix WebExtension 是一个浏览器扩展,用于与 Motrix 下载管理器集成。该扩展允许用户通过 Motrix 下载管理器自动下载文件,而不是使用浏览器的原生下载管理器。…

前端(四)css选择器、css的三大特性

css选择器、css的三大特性 文章目录 css选择器、css的三大特性一、css介绍二、css选择器2.1 基本选择器2.2 组合选择器2.3 交集并集选择器2.4序列选择器2.5属性选择器2.6伪类选择器2.7伪元素选择器 三、css三大特性3.1 继承性3.2 层叠性3.3 优先级 一、css介绍 CSS全称为Casca…

《探索视频数字人:开启未来视界的钥匙》

一、引言 1.1视频数字人技术的崛起 在当今科技飞速发展的时代&#xff0c;视频数字人技术如一颗璀璨的新星&#xff0c;正逐渐成为各领域瞩目的焦点。它的出现&#xff0c;犹如一场科技风暴&#xff0c;彻底改变了传统的视频制作方式&#xff0c;为各个行业带来了前所未有的机…

【ETCD】[源码阅读]深度解析 EtcdServer 的 processInternalRaftRequestOnce 方法

在分布式系统中&#xff0c;etcd 的一致性与高效性得益于其强大的 Raft 协议模块。而 processInternalRaftRequestOnce 是 etcd 服务器处理内部 Raft 请求的核心方法之一。本文将从源码角度解析这个方法的逻辑流程&#xff0c;帮助读者更好地理解 etcd 的内部实现。 方法源码 …

免费下载 | 2024算网融合技术与产业白皮书

《2024算网融合技术与产业白皮书&#xff08;2023年&#xff09;》的核心内容概括如下&#xff1a; 算网融合发展概述&#xff1a; 各国细化算网战略&#xff0c;指引行业应用创新升级。 算网融合市场快速增长&#xff0c;算力互联成为投资新热点。 算网融合产业模式逐渐成型…

基于卷积神经网络的图像二分类检测模型训练与推理实现教程 | 幽络源

前言 对于本教程&#xff0c;说白了&#xff0c;就是期望能通过一个程序判断一张图片是否为某个物体&#xff0c;或者说判断一张图片是否为某个缺陷。因为本教程是针对二分类问题&#xff0c;因此主要处理 是 与 不是 的问题&#xff0c;比如我的模型是判断一张图片是否为苹果…

安全见闻全解析

跟随 泷羽sec团队学习 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及…