计数排序归并排序(递归版本非递归版本)

1.计数排序

        计数排序是一种非比较排序算法,其核心思想是通过统计每个元素出现的次数,然后根据统计结果将元素按照顺序放置在输出数组中。

以下是计数排序的逻辑思想(C语言版):

1. 首先,遍历待排序的数组,找到数组中的最大值max,确定计数数组的大小为max+1。

2. 创建一个大小为max+1的计数数组count,并初始化为0。

3. 遍历待排序的数组,将每个元素的值作为计数数组count的索引,并将对应索引位置的值

加1,表示该元素出现的次数。

4. 对计数数组count进行累加操作,使得每个索引位置的值表示小于等于该索引的元素个数。

5. 统计完次数之后就往原数组内覆盖回写

        计数排序是一种就地排序算法,这意味着它不需要任何额外的空间来对数据进行排序。它也是一个稳定的排序算法,这意味着输入中相等元素的顺序在输出中得以保留。

        计数排序是一种十分高效的排序,时间复杂度为O(n + k),其中n是列表中元素的数量,k是列表中任何元素的最大值。计数排序的空间复杂度为O(k)。

        计数排序是不适合分散的数据,更适合集中的数据;不适合浮点数,字符串,结构体数据排序,只适合整数。

eg:如果有一组数据是从1000开始的连续的数据,那么是不是就意味着前面1000个空间就浪费了,如何解决?

        我是数据是多少就映射到哪个位置上,这种映射叫做绝对映射,这可能会导致大量空间的浪费,大多情况下我们都是使用相对映射的方法。(相对最小值)我们在使用计数排序前,可以先找到改组数据的最小值和最大值,这就是该组数据的范围,这样就可以有效的节省空间了。

代码:

// 计数排序
// 时间:O(N+range)
// 空间:O(range)
void CountSort(int* a, int n)
{int min = a[0], max = a[0];//确定范围for (int i = 1; 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){printf("calloc fail\n");return;}// 统计次数for (int i = 0; i < n; i++){count[a[i] - min]++;//使得下标从0开始}// 排序int i = 0;for (int j = 0; j < range; j++){while (count[j]--){a[i++] = j + min;//回到原数组范围}}
}

2.归并排序 

递归版本
基本思想:
归并排序( MERGE-SORT )是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

它将待排序的数组递归地分成两个子数组,然后对这两个子数组分别进行排序,最后将两个有序的子数组合并成一个有序的数组。

递归版本的归并排序的具体步骤如下:

1. 将待排序的数组分成两个子数组,每个子数组的大小为 n/2。

2. 对这两个子数组分别进行递归排序。

3. 将两个有序的子数组合并成一个有序的数组。

合并两个有序的子数组可以通过以下步骤实现:

1. 创建一个新的数组,用于存储合并后的结果。

2. 将两个子数组中的元素依次比较,将较小的元素放入新的数组中。

3. 重复步骤 2,直到两个子数组中的所有元素都被放入新的数组中。

下面是归并排序的递归版本的算法步骤:

1. **分解**:将待排序的数组分成两个子数组,通过计算数组的中间位置来实现。可以使用递归将左右两个子数组继续分解,直到子数组的大小为1。

2. **排序**:对分解得到的子数组进行排序,可以通过递归调用归并排序函数来实现。

3. **合并**:将排序好的两个子数组合并成一个有序的数组。比较两个子数组的第一个元素,将较小的元素放入结果数组中,并将相应子数组的指针向后移动,重复这个过程直到其中一个子数组为空,然后将另一个子数组中剩余的元素直接放入结果数组中。

递归版本的归并排序的关键在于递归地将数组分解为较小的子数组,并在合并阶段将它们逐步合并成一个有序的数组。这个过程会一直递归下去,直到子数组的大小为1,即只有一个元素,此时可以认为它已经是有序的。

下面归并排序递归版本的实现代码:

void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;// [begin, mid][mid+1, end]_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);// [begin, mid][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++];}else{tmp[i++] = a[begin2++];}}while(begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

非递归版本

递归的思想是把数组分到一个数据认为它有序,然后才开始归并。非递归的思想就是可以一开始就是认为单个数据有序,开分组归并。

这里我们引出gap变量,gap表示每组的数据个数

我们还是通过划分区间的方法来理清逻辑,[begin1,end1] [begin2,end2]两组一归并。

end1为什么是i+gap-1?因为我们访问的是下标,所有我们要减去1

int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;

该如何控制gap?

gap的变化趋势是每次扩大2倍,gap*=2。只要gap<n,那么循环就继续。

该怎么去控制归并循环?

首先i+=2*gap,因为每次要跳过两组数据,那么i的结束条件就可以是i<n


注意:没归并完一组就将这组的数据拷贝回原数组,而不是将所有组归并完后,再将整个数组拷贝回原数组。(稍后解释原因)。

初步代码:

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){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}memcpy(a + i, tmp + i, sizeof(int) * 2*gap);}printf("\n");gap *= 2;}free(tmp);
}
int main()
{int a[8] = { 8,7,6,5,4,3,2,1 };MergeSortNonR(a, 8);for (int i = 0; i < 8; i++){printf("%d ", a[i] );}
}

运行结果:

但我们的数组个数不是2^n时,排序就出现错误了,这是为什么呢?

我们通过打印每组gap的分组情况发现gap为2,4,8时,都出现了越界情况,我们并没有那么多数据,因为我们都是按整数倍去算的,每次都会为我你们补齐,如何解决?

我们先看begin1,begin1是i,它不可能越界,其它的都可能越界。

如果end1越界, 那么证明后面就没有数据,就不需要越界了,接直接break。

如果begin2越界了,end2,那也可以证明后面没有数据了,直接break。

如果end2越界了,那么证明前面都是好的,end2是补齐到了整数倍所以导致越界了,那么只要将end2修正到n就好了。

这里的拷贝也是要进行修改的,因为拷贝也是按照整数倍去拷贝的,要改成(end2-i+1)左闭右闭,减完后要加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){//printf("gap:%2d->", gap);for (size_t i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// [begin1, end1][begin2, end2] 归并//printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);// 边界的处理if (end1 >= n || begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}//printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));}printf("\n");gap *= 2;}free(tmp);
}

运行结果:

填前面的坑:为什么要归并一组拷贝一组,而不是等拷贝完了,拷贝整个数组?

因为会出现越界的情况,当遇到越界的情况,因为只有一组已经是有序的了,直接break就好了,就不需要进入tmp数组了,如果break了,却又整组的进行迭代数据,那么在最后一组的位置进入tmp数组就有可能出现随机值,再拷贝回原数组那么就会把最后一组正确的数据给覆盖掉了。

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

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

相关文章

[足式机器人]Part2 Dr. CAN学习笔记- Kalman Filter卡尔曼滤波器Ch05-3+4

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - Kalman Filter卡尔曼滤波器 Ch05-34 3. Step by step : Deriation of Kalmen Gain 卡尔曼增益/因数 详细推导4. Priori/Posterrori error Covariance Martix 误差协方差矩阵 3. Step by step :…

Spring Boot - 利用Resilience4j-Circuitbreaker实现断路器模式_防止级联故障

文章目录 PreResilience4j概述Resilience4j官方地址Resilience4j-Circuitbreaker应用场景微服务演示Address servicePOMModelRepositoryServiceControllerData InitProperties测试 Order serviceModelRepositoryServiceSet UpProperties测试 探究断路器调用order-service API 2…

Java网络编程——UDP通信原理

一、TCP和UDP概述 传输层通常以TCP和UDP协议来控制端点与端点的通信 TCPUDP协议名称传输控制协议用户数据包协议是否连接面向连接的协议。数据必须要建立连接无连接的协议&#xff0c;每个数据报中都给出完整的地址信息&#xff0c;因此不需要事先建立发送方和接受方的连接是…

leetcode 013二维区域和检索---矩阵不可变

给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的左上角为 (row1, col1) &#xff0c;右下角为 (row2, col2) 。 实现 NumMatrix 类&#xff1a; NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进…

jrebel IDEA 热部署

1 下载 2022.4.1 JRebel and XRebel - IntelliJ IDEs Plugin | Marketplace 2 选择下载好的zip 离线安装IDEA 插件 重启IDEA 3 打开 [Preference -> JRebel & XRebel] 菜单&#xff0c;输入 GUID address 为 https://jrebel.qekang.com/1e67ec1b-122f-4708-87d…

买家福音:亚马逊鲲鹏系统全自动操作助你轻松搞定一切

我一直以来都是亚马逊的忠实用户&#xff0c;但是最近我发现了一款真正令人惊叹的工具&#xff0c;改变了我在平台上的经验。我想分享一下我的感受&#xff0c;最近&#xff0c;我得知并尝试了亚马逊鲲鹏系统&#xff0c;简直是为买家账号管理量身定制的利器。在我账号过多时&a…

旅游行业@2023/24:复苏、繁荣与重构

Mark作为一名热爱旅游的大学生&#xff0c;在2023年&#xff0c;去过很多地方&#xff0c;有因烧烤爆火的淄博&#xff1b;也有因特种兵式旅游再次出名的泰山&#xff1b;还和同学一起自驾去了川西&#xff1b;最近&#xff0c;他就正在计划春节要在哪里度过。 “我可太喜欢旅…

公司用什么软件监控电脑—可以部署|天锐绿盾数据防泄密系统(有行为审计监控功能)防止核心文件数据资料外泄!

天锐绿盾是一款企业内网安全管理软件&#xff0c;专注于为企业提供全面的信息化安全管理解决方案。它通过局域网内文件的透明加密、内网的有效管理等方式&#xff0c;满足不同类型企业用户对信息安全的需求。 天锐绿盾是一款功能强大的企业内网安全管理软件 PC地址&#xff1a;…

MSVS C# Matlab的混合编程系列1 - 看似简单的问题引出

前言&#xff1a; 问题提出&#xff0c;如何把Matlab(本文简称MT)的算法集成到Visual Studio(本文简称VS)里面运行&#xff1f; 本文&#xff0c;通过编制一个MT中最简单的加法函数&#xff0c;我们把他做成 MSVS C#能够使用的动态库&#xff0c;说明了MSVS C# 和 MT集成的最…

GO基础进阶篇 (十四)、Http编程

Web基础概念 web应用程序 web程序可以提供浏览器访问的程序。Web应用程序通常采用客户端-服务器模型。客户端是用户使用的Web浏览器或其他Web客户端&#xff0c;而服务器是存储和处理数据的远程计算机。 我们能访问到的任何一个页面或资源&#xff0c;都存在于世界的某一个角落…

css 居中方式

居中分为水平居中和垂直居中 1. 水平居中1.1 文字text-align:center;1.2 盒子1.2.1&#xff1a;inline-block text-align 一 center;1.2.2&#xff1a;absolutetransform 一 父元素 display:relative;子元素 display:absolute; left:50%;transform: translatex(-50%);1.2.3&a…

Git教程学习:03 记录每次更新到仓库

文章目录 1 检查当前文件状态2 跟踪新文件3 暂存已修改的文件4 状态简览5 忽略文件6 查看已暂存和未暂存的修改7 提交更新8 跳过使用暂存区域9 移除文件10 移动文件 现在我们的机器上有了一个 真实项目 的 Git 仓库&#xff0c;并从这个仓库中检出了所有文件的 工作副本。 通常…