决战排序之巅(二)

决战排序之巅(二)

        • 排序测试函数 void verify(int* arr, int n)
    • 归并排序
      • 递归方案
          • 代码可行性测试
      • 非递归方案
          • 代码可行性测试
      • 特点分析
    • 计数排序
      • 代码实现
          • 代码可行性测试
      • 特点分析
    • 归并排序 VS 计数排序(Release版本)
      • 说明
        • 1w rand( ) 数据测试
        • 10w rand( ) 数据测试
        • 100w rand( ) 数据测试
        • 1000w rand( ) 数据测试
      • 测试代码
      • 结语

欢迎来到决战排序之巅栏目,
本期给大家带来的是归并排序与计数排序的实现与比较。
在上期决战排序之巅(一)中,给大家带来了插入排序(希尔) 与 选择排序(堆排) 的实现与比较,感兴趣的可以看看。

请添加图片描述

排序测试函数 void verify(int* arr, int n)

主要功能:测试arr数组中的顺序是否全为非升序的顺序。
代码如下:

void verify(int* arr, int n)
{for (int i = 1; i < n; i++){assert(arr[i] >= arr[i - 1]);}
}

如果arr数组中顺序不全为非升序,则assert()直接终止程序;
若全为非升序,则程序可通过该函数。

归并排序

基本思想:采用分治算法,将已有的有序子序列进行合并,得到完全有序的序列;即先使每个子序列有序,再使子序列所合并的序列有序。
归并排序的核心步骤就是:分解与合并。

递归方案

如下图所示:我们可以先将一组数据由大到小逐个分开,再依次合并。
下图绿线为分解,蓝线为合并。我们可以看到,排序数据分解时,当子序列内个数为1 时,不再分解;随后进行依次的合并,"1" "9" 合并为"1 9"的子序列,"5" "6"合并成"5 6"的体序列,同理可得"3 8" "2 7",再让子序列合并,"1 9 6 5"合并成"1 5 6 9""3 8""2 7"合并成"2 3 7 8"。最后两个字序列合并成"1 2 3 5 6 7 8 9"
至此,归并排序完毕。
在这里插入图片描述
具体代码,如下:

void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

void MergeSort(int* a, int n)是我们排序的调用函数,因为他的参数形式不宜用递归实现,所以我们可以写一个子函数void _MergeSort(int* a,int begin,int end ,int* tmp)来实现主要程序的编写,如下:

void _MergeSort(int* a,int begin,int end ,int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);int left1 = begin, right1 = mid;int left2 = mid + 1, right2 = end;int i = 0;while (left1 <= right1 && left2 <= right2){if (a[left1] > a[left2])tmp[i++] = a[left2++];elsetmp[i++] = a[left1++];}while (left1 <= right1){tmp[i++] = a[left1++];}while (left2 <= right2){tmp[i++] = a[left2++];}memcpy(a + begin, tmp, i * sizeof(int));
}

我们先通过以下代码进行归并排序“分解”的实现

	if (begin >= end)return;	int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);

当子序列内个数为1 时,return 返回;当子序列内个数大于1 时,进行以下编写:
有递归可知,此时的小标区间为[begin , mid] 与 [mid + 1 , end]是排好序的子区间,所有此时我们只要将其合并好就可以了。

	int left1 = begin, right1 = mid;int left2 = mid + 1, right2 = end;int i = 0;while (left1 <= right1 && left2 <= right2){if (a[left1] > a[left2])tmp[i++] = a[left2++];elsetmp[i++] = a[left1++];}while (left1 <= right1){tmp[i++] = a[left1++];}while (left2 <= right2){tmp[i++] = a[left2++];}memcpy(a + begin, tmp, i * sizeof(int));

最后将tmp上的数据拷贝到a的[begin , end]区间即可。

代码可行性测试
void _test()
{int n = 100000000;int* arr = (int*)malloc(sizeof(int) * n);for (int i = 0; i < n; i++){arr[i] = rand();}MergeSort(arr, n);verify(arr, n);free(arr);
}

运行结果如下 :
在这里插入图片描述
程序通过verify(int* arr int n)函数,且成功运行,代码无误。

非递归方案

在非递归方案中我们可以利用循环来实现,主要实现过程如下视频所示:

归并排序思想

我们可以定义一个gap并且gap的初始置为1,用来表示子序列的最小个数为1,随后在整体排完相邻两个子序列后,gap乘以2,此时数组内小标区间为 [ n ∗ g a p , n ∗ ( g a p ∗ 2 − 1 ) ] ∪ [ 0 , g a p − 1 ] , n ∈ N + [n * gap , n * (gap * 2-1)]\cup[0 , gap-1] ,n\in N^+ [ngap,n(gap21)][0,gap1],nN+是有序的,如此循环直到, n ≤ g a p n\leq gap ngap时跳出循环,代码如下:

void MergeSortNonR(int* a,int n)
{int* tmp = (int*)malloc(sizeof(int) * n);assert(tmp);int gap = 1;while (n > gap){for (int i = 0; i < n; i += gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap * 2 - 1;int j = begin1;if (end1 >= n && begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}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));}gap *= 2;}free(tmp);
}

我们先看如何分解,利用gap来确定子序列的元数个数,再利用for循环来实现两个相邻子序列的排序(即下标区间[begin1,end1] , [begin2,end2]的排序)
注意:在分配完区间[begin1,end1] ,和[begin2,end2]后,我们要对区间范围的有效性进行检查,因为非递归的方案通过比较相邻的子序列,gap2的幂次方所增长,适用的数组长度也为2的幂次方,所以我们要对end1 , begin2 , end2进行检查,如果end1 , begin2 大于数组总个数n时,直接break即可,因为此时的[begin1,n-1]已经是有序的了;如果end2大于n则,令end2=n-1,此时我们只要排好[begin1,end2] , [begin2,n-1]即可,具体过程如下:

		for (int i = 0; i < n; i += gap * 2){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap * 2 - 1;int j = begin1;if (end1 >= n && begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}//合并过程}

合并过程与递归方案相同,但需要注意的是数组拷贝的时候,for循环依次拷贝一次。

代码可行性测试

在这里插入图片描述

程序通过verify(int* arr int n)函数,且成功运行,代码无误。

特点分析

特性:归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定性:稳定

计数排序

基本思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

代码实现

实现步骤:

  1. 选出要排序数组a中的最值,再相减求出数组的相对范围 n = m a x − m i n + 1 n = max - min + 1 n=maxmin+1
  2. 用calloc开辟n个空间为tmp
  3. 利用i遍历a,让数组tmp[ a [ i ] − m i n a[i] - min a[i]min]++
  4. 最后,再遍历tmp , 此时tmp数组下标 + min就表示数据的大小,tmp[数组下标]表示该数据的个数,所以在此时为a直接赋值即可。
    具体代码如下:
void CountSort(int* a, int n)
{int max = a[0], min = a[0];int i = 0;for (i = 0; i < n; i++){if (max < a[i]){max = a[i];}if (min > a[i]){min = a[i];}}int* tmp = (int*)calloc((max - min + 1), sizeof(int));assert(tmp);for (i = 0; i < n; i++){tmp[a[i] - min]++;}int j = 0;for (i = 0; i < max - min + 1; i++){int count = tmp[i];while (count--){a[j++] = i + min;}}free(tmp);
}
代码可行性测试

在这里插入图片描述
程序通过verify(int* arr int n)函数,且成功运行,代码无误。

特点分析

特点分析:计数排序在数据范围集中时,效率很高,但是适用范围及场景有限(例如:小数,结构体,字符串无法比较)
时间复杂度:O(MAX(N,范围))
空间复杂度:O(范围)

归并排序 VS 计数排序(Release版本)

说明

以下会分别对1w,10w,100w,1000w的数据进行100次的排序比较,并计算出排一趟的平均值。

下面是用来生成随机数的代码,可以确保正数与负数的随机分布。

	for (i = 0; i < n; i++){if (rand() % 2){arr3[i] = arr2[i] = arr1[i] = -rand() + i;}else{arr3[i] = arr2[i] = arr1[i] = rand() - i;}}

介绍就到这里了,让我们来看看这100次排序中,谁才是你心目中的排序呢?
PS:100次只是一个小小的测试数据,有兴趣的朋友可以在自己电脑上测试更多的来比较哦。

1w rand( ) 数据测试

在这里插入图片描述

10w rand( ) 数据测试

在这里插入图片描述

100w rand( ) 数据测试

在这里插入图片描述

1000w rand( ) 数据测试

在这里插入图片描述

测试代码

void Test_MergeSort_CountSort()
{int n = 10000000;int count = 100;int* arr1 = numcreate(n);int* arr2 = numcreate(n);int* arr3 = numcreate(n);int time1 = 0, time2 = 0, time3 = 0;int tmp = count;while (tmp--){int i = 0;for (i = 0; i < n; i++){if (rand() % 2){arr3[i] = arr2[i] = arr1[i] = -rand() + i;}else{arr3[i] = arr2[i] = arr1[i] = rand() - i;}}int begin1 = clock();MergeSort(arr1, n);int end1 = clock();int begin2 = clock();MergeSortNonR(arr2, n);int end2 = clock();int begin3 = clock();CountSort(arr3, n);int end3 = clock();time1 += end1 - begin1;time2 += end2 - begin2;time3 += end3 - begin3;}printf("MergeSort: %.2f\n", (float)time1/count);printf("MergeSortNonR: %.2f\n", (float)time2 / count);printf("CountSort: %.2f\n", (float)time3 / count);free(arr1);free(arr2);free(arr3);
}

从结果来看,计数排序快于归并排序,但它的局限性无法比较小数,结构体与字符串;
再看归并排序,非递归类的要略胜一筹哦。

结语

看完之后,谁才是你心目中的排序呢?
欢迎留言,让我们一起来期待在下一期 《决战排序之巅(三)》。

以上就是本期的全部内容喜欢请多多关注吧!!!

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

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

相关文章

MATLAB - 加载预定义的机器人模型

系列文章目录 前言 一、 要快速访问常见的机器人模型&#xff0c;可使用 loadrobot 功能&#xff0c;该功能可加载市售的机器人模型&#xff0c;如 Universal Robots™ UR10 cobot、Boston Dynamics™ Atlas 人形机器人和 KINOVA™ Gen 3 机械手。探索如何生成关节配置并与机器…

小程序基础库与Android之间通信优化的可能

最近在学习graalvm&#xff0c;发现有一个graaljs项目&#xff0c;项目中介绍可以让java与JavaScript做数据转换&#xff0c;比如JavaScript中可以使用java的数据类型与结构。突然想到之前遇到的一个问题&#xff0c;小程序中开发的代码和基础库的部分代码都是j2v8来执行的&…

深入理解 Spark(二)SparkApplication 提交和运行源码分析

spark 核心流程 yarn-client yarn-cluster spark 任务调度 spark stage 级别调度 spark task 级别调度 失败重试和白名单 对于运行失败的 Task&#xff0c;TaskSetManager 会记录它失败的次数&#xff0c;如果失败次数还没有超过最大重试次数&#xff0c;那么就把它放回待调…

【Docker构建MySQL8.0镜像】

Docker构建MySQL8.0镜像 部署流程1. 拉取docker镜像2. 创建数据卷&#xff0c;存放MySQL数据3. 启动MySQL镜像4. 初始化sql放入MySQL镜像5. 执行MySQL脚本6. MySQL镜像打包7. MySQL镜像迁移 部署流程 1. 拉取docker镜像 docker pull mysql:8.0.35拉取成功后就可以看到镜像了&…

NFS(Network File System 网络文件服务)

一&#xff0c;nfs 简介 1&#xff0c;nfs 性质 NFS&#xff08;Network File System 网络文件服务&#xff09; 文件系统&#xff08;软件&#xff09;文件的权限 NFS 是一种基于 TCP/IP 传输的网络文件系统协议 通过使用 NFS 协议&#xff0c;客户机可以像访问本地目录一样…

【NI国产替代】NI-9231,8通道,51.2 kS/s/ch,-5至5 V,C系列声音和振动输入模块

8通道&#xff0c;51.2 kS/s/ch&#xff0c;-5至5 V&#xff0c;C系列声音和振动输入模块 NI-9231是一款高密度声音和振动模块&#xff0c;能够测量来自速度计、转速计和接近式探针等集成电子压电(IEPE)和非IEPE传感器的信号。该模块可以执行现代麦克风和加速计所需的高动态范围…

verilog编程题

verilog编程题 文章目录 verilog编程题序列检测电路&#xff08;状态机实现&#xff09;分频电路计数器译码器选择器加减器触发器寄存器 序列检测电路&#xff08;状态机实现&#xff09; module Detect_101(input clk,input rst_n,input data,o…

力扣每日一练(24-1-15)

重复n次检查&#xff0c;几乎都用双指针。。。 固然双指针就是题解&#xff1a; if len(nums) < 3:return len(nums)p1 2 for p2 in range(2, len(nums)):if nums[p2] ! nums[p1 - 2]:nums[p1] nums[p2]p1 1return p1 可以重复两次&#xff0c;那么隔一个检查就行&#…

vtk9.3 配置 visual studio 2019 运行环境 和运行实例详解

&#xff08;1&#xff09;包含文件配置&#xff1a; 项目--属性--VC目录&#xff0c;在包含目录中把include文件夹的地址加进去&#xff0c;一直要到下一级 vtk-9.3目录下&#xff0c; 小知识&#xff1a; 在Visual Studio 2019中运行项目时&#xff0c;如果项目中使用了第三…

网络安全笔记-SQL注入

文章目录 前言一、数据库1、Information_schema2、相关函数 二、SQL注入分类1、联合查询注入&#xff08;UNION query SQL injection&#xff09;语法 2、报错注入&#xff08;Error-based SQL injection&#xff09;报错注入分类报错函数报错注入原理 3、盲注布尔型盲注&#…

【Java 设计模式】创建型之抽象工厂模式

文章目录 1. 定义2. 应用场景3. 代码实现4. 应用示例结语 在软件开发中&#xff0c;抽象工厂模式是一种常见的创建型设计模式&#xff0c;它提供了一种创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。抽象工厂模式的核心思想是将一组相关的产品组合成…

docker screen 常用基础命令

1.docker基础命令 1.1开启docker systemctl start docker #开启docker service docker restart #重启docker systemctl stop docker #关闭docker 1.2查看命令 docker images #查看docker镜像docker ps #查看正在运行的镜像或者容器docker ps -a #查看所有容器1.3运…

刘知远LLM入门到实战——自然语言基础

文章目录 自然语言处理基础词表示语言模型N-gram ModelNeural Language Model: 为什么NLP等领域的模型越来越大&#xff1f; 大模型会带来哪些新的范式和挑战&#xff1f; 自然语言处理基础 让计算机理解人类语言&#xff0c;图灵测试就是基于对话的方式。 研究历史&#xff…

FlinkAPI开发之处理函数

案例用到的测试数据请参考文章&#xff1a; Flink自定义Source模拟数据流 原文链接&#xff1a;https://blog.csdn.net/m0_52606060/article/details/135436048 概述 之前所介绍的流处理API&#xff0c;无论是基本的转换、聚合&#xff0c;还是更为复杂的窗口操作&#xff0c…

cron表达式大全

简介 Cron表达式是一种用于指定定时任务的时间表达式&#xff0c;常用来指定任务的执行时间、执行频率和执行间隔。它由6&#xff5e;7个字段组成&#xff0c;分别表示秒、分、时、日期、月份、星期、年份&#xff08;可省略&#xff09;。 Cron表达式基本语法如下&#xff1a;…

人工智能 | ChatGPT 和文心一言哪个更好用?

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 ChatGPT 和文心一言哪个更好用&#xff1f; ChatGPT 和文心一言哪个更好用&#xff1f;方向一&#xff1a;ChatGPT主要优势局限性和挑战如何克服chatGPT的局限性和挑战…

全罐喂养一个月多少钱?适合全罐喂养的猫罐头推荐

不少铲屎官一直没有办法get到猫罐头对猫咪的好处&#xff0c;或者get到了又觉得给猫咪买猫罐头好贵&#xff0c;看到其他铲屎官都开始全罐喂养了&#xff0c;但是自己却迟迟下不了手&#xff0c;犹犹豫豫的不知道全罐喂养一个月多少钱&#xff1f; 今天&#xff0c;铲龄15年的…

Python实现Excel切片删除功能(附源码)

使用Python实现Excel中的“切片删除”功能&#xff08;附源码&#xff09; 先上效果图&#xff01;&#xff01;&#xff01; 如下是需要处理的Excel文件&#xff0c;可以看到在27行和117行处的E列存在数据不一致情况&#xff0c;需进行“切片删除” 运行结果图如下&#xff…

【前端架构】前端通用架构

一个强大的前端通用架构应该具备多种能力&#xff0c;以支持现代化的应用程序开发和提高开发效率。以下是一些前端通用架构应该具备的关键能力&#xff1a; 模块化和组件化&#xff1a;支持模块化开发和组件化架构&#xff0c;能够将应用拆分为独立的模块和组件&#xff0c;以便…

leetcode 2418. 按身高排序

题目 给你一个字符串数组 names &#xff0c;和一个由 互不相同 的正整数组成的数组 heights 。两个数组的长度均为 n 。 对于每个下标 i&#xff0c;names[i] 和 heights[i] 表示第 i 个人的名字和身高。 请按身高 降序 顺序返回对应的名字数组 names 。 解题方法&#xff…