C语言数据结构:排序(2)

在这里插入图片描述

文章目录

  • 4.选择排序
    • 4.1 基本思想
    • 4.2 排序实现
  • 5.快排
    • 5.1 基本思想
    • 5.2 递归实现快排
    • 5.3 快排优化
    • 5.4 双指针法完成快排
    • 5.5 快排的非递归
  • 6.归并排序
    • 6.1 基本思想
    • 6.2 排序的实现
    • 6.3 归并排序的非递归
  • 7. 计数排序
    • 7.1 基本思想
    • 7.2 排序实现
    • 7.3 排序的优缺点
  • 8.排序总结

4.选择排序

4.1 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完

4.2 排序实现

我们先来写一下,单次的排序:

int begin = 0, end = n - 1;
int maxi = begin, mini = begin;
for (int i = begin + 1; i <= end; i++)
{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]);

这里要注意的问题:

这里执行连续的交换是如果下标重叠就会出问题

也就是当maxi和mini同时选中一个下标时,就会出现一些问题

void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int maxi = begin, mini = begin;for (int i = begin + 1; i <= end; i++){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--;}
}

5.快排

5.1 基本思想

left找比key大的数
right找比key小的数
将left和right的数交换直到相遇
最后将相遇的位置和key交换

1729647889982

我们会发现这个和二叉树有一些相似的,运用递归就可以解决了

5.2 递归实现快排

首先还是要实现单趟的排序:

	int key = a[left];int begin = left, end = right;while (begin < end){//begin < end加判断防止直接走到下一个大的数while (begin < end && a[end] >= key){end--;}while (begin < end && a[begin] <= key){begin++;}Swap(&a[end], &a[begin]);}Swap(a[left], &a[begin]);

接着直接使用递归就可以了,和二叉树的实现方法差不多

void QuickSort(int* a, int left, int right)
{//递归结束条件if (left >= right){return;}int key = a[left];int begin = left, end = right;while (begin < end){//begin < end加判断防止直接走到下一个大的数while (begin < end && a[end] >= key){end--;}while (begin < end && a[begin] <= key){begin++;}Swap(&a[end], &a[begin]);}Swap(&a[left], &a[begin]);key = begin;//找到中间位置//分割区间[left,key-1][key+1,right]//让区间有序QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

1729650007450

5.3 快排优化

这样的快排有问题:

在同时排乱序的情况下,效率差距不大,但是当对已经有序的数组排序的时候,就显得效率太低了

1729650785217

1729653019944

这里主要的问题是每次选取的key都在数组的最前面,导致的简单问题复杂化

我们可以采取yi优化:

  1. 随机选key,但是有些不靠谱

  2. 三数取中:三个数中选不是最大也不是最小的数为key

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]){return right;}else{return left;}}else //a[left] > a[midi]{if (a[midi] > a[right]){return midi;}else if (a[right] < a[left]){return left;}else{return right;}}
}

这样对于有序列表就排序的快了一些了:

1729654064537

我们还可以在优化一下:

在数据比较少的等候,仍然进行递归让整体的程序运行速度变得慢了,可与采取其余方式进行排序

svoid QuickSort(int* a, int left, int right)
{//递归结束条件if (left >= right){return;}if ((right - left) + 1 < 10){InsertSort(a + left, (right - left) + 1);}else{int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int key = a[left];int begin = left, end = right;while (begin < end){//begin < end加判断防止直接走到下一个大的数while (begin < end && a[end] >= key){end--;}while (begin < end && a[begin] <= key){begin++;}Swap(&a[end], &a[begin]);}Swap(&a[left], &a[begin]);key = begin;//找到中间位置//分割区间[left,key-1][key+1,right]//让区间有序QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

5.4 双指针法完成快排

先来看一下思路:

前后指针

  1. 这里让指针prev先走
  2. 指针cur 去找到比key小的数
  3. 找到比key小的数,此时如果prev++cur不在一个位置时交换
  4. cur出界时将keyprev交换

写成代码如下:

void QuickSort02(int* a, int left, int right)
{if (left >= right){return;}int cur = left + 1;int prev = left;int key = left;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);key = prev;QuickSort02(a, left, key - 1);QuickSort02(a, key + 1, right);
}

5.5 快排的非递归

为什么要改成非递归呢?

如果递归的深度太深会导致栈溢出

那如何实现呢?

image-20241027091541830

如图我们借助栈,来存放我们的right和left来让中间完成遍历

我们将数组分开按照深度优先遍历的方式来实现

写成代码:

int PartSort02(int* a, int left, int right)
{int prev = left;int cur = left + 1;int key = left;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}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 key = PartSort02(a, begin, end);if (key + 1 < end)//因为需要先选begin,所以要先入end后入begin来取begin{STPush(&st, end);STPush(&st, key + 1);}if (begin < key - 1){STPush(&st, key - 1);STPush(&st, begin);}}STDestroy(&st);
}

循环每走一次就相当于之前的递归

image-20241027091700543

6.归并排序

6.1 基本思想

下来看一下思路:

归并排序

过程如下:

image-20241027094526321

6.2 排序的实现

用递归的方法还是比较好写的:

void _MergeSort(int* a, int* tmp, int begin, int end)
{if (begin >= end){return;}int mid = (begin + end) / 2;_MergeSort(a, tmp, begin, mid);_MergeSort(a, tmp, mid + 1, end);//递归后已经有序了 归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i] = a[begin1];i++;begin1++;}else{tmp[i] = a[begin2];i++;begin2++;}}while (begin1 <= end1){tmp[i] = a[begin1];i++;begin1++;}while(begin2 <= end2){tmp[i] = a[begin2];i++;begin2++;}memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}void MergeSort(int* a, int n)
{int* tmp = malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");return;}_MergeSort(a, tmp, 0, n - 1);free(tmp);tmp = NULL;
}

时间复杂度:N*logN

一层的时间复杂度为logN,一共有N层,所以时间复杂度为N*logN

6.3 归并排序的非递归

这里就没法用栈了,以为将其出栈后还要进行归并就还要用一个栈进行存储,就有些麻烦了

那我们直接进行循环处理

思路如下:

image-20241027111248017

实现代码:

void _MergeSort(int* a, int* tmp, int left, int right)
{//递归将数组分开if (left >= right){return;}int mid = (right + left) / 2;_MergeSort(a, tmp ,left, mid);_MergeSort(a,tmp , mid + 1, right);//分开后,进行归并排序int i = left;int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i] = a[begin1];i++;begin1++;}else{tmp[i] = a[begin2];i++;begin2++;}}//上面进行完,还有一组没有进行完,这里直接使用while,就不用if了while (begin1 <= end1){tmp[i] = a[begin1];begin1++;i++;}while (begin2 <= end2){tmp[i] = a[begin1];begin2++;i++;}memcpy(a + left, tmp + left, (right - left + 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;
}

但是这里会存在一些越界的问题:

image-20241027133904563

  1. 这里针对后两种的方式我们可以采取将其不在使用归并排序了
  2. 针对第一种则可以将其写改一下在直接排即可

写成代码如下:

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");return;}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = gap + i, end2 = i + gap * 2 - 1;//判断越界(第二组不存在不用归并)if (begin2 >= n){break;}if (end2 >= n)//只有第二组的end越界了,修正以下继续归并就行了{end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j] = a[begin1];j++;begin1++;}else{tmp[j] = a[begin2];j++;begin2++;}}while (begin1 <= end1){tmp[j] = a[begin1];j++;begin1++;}while (begin2 <= end2){tmp[j] = a[begin2];j++;begin2++;}memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);tmp = NULL;
}

7. 计数排序

7.1 基本思想

1730083921172

本质:利用count数组的自然序号排序

那么count数组的空间怎么开呢?

  1. 直接从最小到最大的数据来开,但是会造成空间浪费(也叫绝对映射)
  2. 找到最小的数,将最大的数减去最小的数,然后开空间,后再加上最小的数(相对映射)

7.2 排序实现

void CountSort(int* a, int n)
{int min = a[0], max = a[0];//选出最大最小值for (int i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)calloc( range, sizeof(int));if (count == NULL){perror("calloc fail");return;}//统计次数for (int i = 0; i < n; i++){count[a[i] - min]++;}int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j] = i + min;j++;}}free(count);count = NULL;
}

7.3 排序的优缺点

这样的相对映射可以应对负数

1730086458144

时间复杂度为 O(N+range)

  • 但是这个排序仅适用于整数且这些数范围要比较集中
  • 而且空间复杂度也是比较高的

在满足这个条件下这个排序的性能是非常快的

8.排序总结

1730096837079

稳定性指的是相同值相对顺序是否一致,例如:

1730097208893

  1. 插入排序–稳定

  2. 希尔排序–不稳定

    相同的数据预排序的时候分到不同的组,无法控制

  3. 选择排序–不稳定

    在最后一次交换的时候,很有可能让确保不了最后的交换顺序

1730098995528

  1. 堆排序–不稳定

1730099125488

  1. 冒泡排序–稳定

  2. 快速排序–不稳定

    key换到中间的时候也是不可控的

  3. 归并排序–稳定

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

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

相关文章

C++初阶教程——C++入门

一、本章主要内容 C在C的基础之上&#xff0c;加入了面向对象编程的思想&#xff0c;并增加了许多有用的库以及编程范式。可以说&#xff0c;C是C的子集。在这章的内容中&#xff0c;笔者将会为诸位读者讲C如何补充C语言的一些不足。比如&#xff1a;作用域、IO、函数、指针等。…

【Golang】Go语言中如何进行包管理

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

JetBrains IDE中GPU进程(JCEF)重启问题(Too many restarts of GPU-process)解决方案

目录 前言1. GPU进程重启问题概述1.1 什么是GPU进程重启问题&#xff1f;1.2 该问题带来的影响 2. GPU进程重启问题的原因分析2.1 显卡驱动的兼容性问题2.2 系统资源的限制2.3 JCEF组件的设置不合理 3. 解决方案3.1 方法一&#xff1a;通过自定义属性禁用GPU加速3.2 方法二&…

20241028在荣品PRO-RK3566开发板的预置Android13下用iperf3测试AP6256的WIFI网速

20241028在荣品PRO-RK3566开发板的预置Android13下用iperf3测试AP6256的WIFI网速 2024/10/28 18:17 荣品PRO-RK3566开发板作为服务器端&#xff1a; 笔记本电脑作为客户端。 接公司的网络。 在笔记本电脑的ubuntu20.04下&#xff0c;通过nethogs实测iperf3的发送速度大概是10MB…

410wifi的正确利用之路——debian 11

装上openwrtadguard太卡了&#xff0c;10min自启一次&#xff0c;当无线网卡都费劲。 网桥、USB千万网段要和主网独立 wifi连接激活后再改静态ip 高通410 修复debian 11的环境 1 换debian11源 &#xff08;1&#xff09;切换为国内的软件源&#xff08;可以加快软件更新时的…

压力测试Monkey命令参数和报告分析!

adb的操作命令格式一般为&#xff1a;adb shell monkey 命令参数 PART 01 常用参数 ⏩ -p <测试的包名列表> 用于约束限制&#xff0c;用此参数指定一个或多个包。指定包之后&#xff0c;Monkey将只允许系统启动指定的APP。如果不指定包&#xff0c;Monkey将允许系统…

SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测

SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测 目录 SSA-CNN-LSTM-MATT多头注意力机制多特征分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现SSA-CNN-LSTM-MATT麻雀算法优化卷积神经网络-长短期记忆神经网络融合多头注意力机制多特征分类预测&…

GPU 服务器厂家:中国加速计算服务器市场的前瞻洞察

科技的飞速发展&#xff0c;让 GPU 服务器在加速计算服务器领域的地位愈发凸显。中国加速计算服务器市场正展现出蓬勃的生机&#xff0c;而 GPU 服务器厂家则是这场科技盛宴中的关键角色。 从市场预测的趋势来看&#xff0c;2023 年起&#xff0c;中国加速计算服务器市场便已展…

Go第三方框架--gorm框架(二)

增删改查&#xff08;dml操作&#xff09; 查询操作 gorm查询主要执行了三种操作&#xff1a; 通过链式函数调用累计查询条件&#xff08;在map[string]clause.Clause中累计&#xff09;将查询条件转换成sql&#xff08;赋值给 Statement.SQL和Statement.Vals&#xff09;执…

A Simple Semi-Supervised Learning Framework for Object Detection

1. Introduction SSL的成功主要有以下两个方面&#xff1a; &#xff08;1&#xff09;一致性正则化&#xff1a;如果对一个未标记的数据应用实际的扰动, 其预测结果不应该发生显著变化, 也就是输出具有一致性&#xff0c;通过在未标记数据上构造添加扰动后的预测结果 y~​ 与…

【51 Pandas+Pyecharts | 深圳市共享单车数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 处理起始时间、结束时间2.4 增加骑行时长区间列2.5 增加骑行里程区间列 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 各…

webGlL变量的声明与使用

抢先观看&#xff1a; 变量的声明格式&#xff1a;<存储限定符><类型限定符><变量名> 存储限定符&#xff1a;const, attribute, uniform, varying, buffer。 类型限定符&#xff1a;void, bool, int, float, double, vec2, vec3, vec4, mat2, mat3, mat4, s…

基于SSM的成都市旅游信息管理系统-计算机毕业设计源码65815

SSM成都市旅游信息管理系统 摘 要 本论文主要论述了如何使用SSM框架开发一个旅游信息管理系统&#xff0c;严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构JAVA技术&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述旅游信息管理系…

91.【C语言】数据结构之单向链表的头删和尾删

目录 1.尾删函数SLTPopBack 代码示例(写入SList.c) 在SList.h中写入该函数的声明 main.c部分代码改为 ​编辑 分析 解决方法 方法1:双指针算法(快指针tail,慢指针pretail) 方法2 2.头删函数SLTPopFront 一个节点示意图 多个节点示意图 代码示例(写入SList.c) 在S…

DEVOPS: 集群伸缩原理

概述 阿里云 K8S 集群的一个重要特性&#xff0c;是集群的节点可以动态的增加或减少有了这个特性&#xff0c;集群才能在计算资源不足的情况下扩容新的节点&#xff0c;同时也可以在资源利用 率降低的时候&#xff0c;释放节点以节省费用理解实现原理&#xff0c;在遇到问题的…

华为OD机试 - 无向图染色(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

云智慧完成华为原生鸿蒙系统的适配, 透视宝 APM 为用户体验保驾护航

2024 年 10 月 22 日&#xff0c;首个国产移动操作系统 —— 华为原生鸿蒙操作系统 HarmonyOS NEXT 正式面世&#xff0c;成为继 iOS 和 Android 后的全球第三大移动操作系统。HarmonyOS NEXT&#xff0c;从系统内核、数据库根基&#xff0c;到编程语言创新、AI&#xff08;人工…

无人机之任务分配算法篇

无人机的任务分配算法是无人机系统中的重要组成部分&#xff0c;它决定了无人机如何高效、合理地执行各种任务。以下是一些常见的无人机任务分配算法&#xff1a; 一、合同网协议&#xff08;Contract Net Protocol, CNP&#xff09; 基本概念&#xff1a;CNP算法是一种分布式…

【WRF数据处理】基于GIS4WRF插件将geotiff数据转为tiff(geogrid,WPS所需数据)

【WRF数据处理】基于GIS4WRF插件将geotiff数据转为tiff&#xff08;geogrid&#xff0c;WPS所需数据&#xff09; 数据准备&#xff1a;以叶面积指数LAI为例QGis实操&#xff1a;基于GIS4WRF插件将geotiff数据转为tiff警告&#xff1a;GIS4WRF: Input layer had an unexpected …

【MySQL基础】高级查询

文章目录 一、聚合函数&#xff1a;COUNT、SUM、AVG、MIN、MAX1. 统计总数&#xff1a;COUNT2. 计算总和&#xff1a;SUM3. 计算平均值&#xff1a;AVG4. 找最小值&#xff1a;MIN5. 找最大值&#xff1a;MAX 综合使用聚合函数的例子小结 二、分组查询——GROUP BY 和 HAVING1.…