数据结构之排序一

目录

1.排序

一.概念及其分类

二.排序的稳定性

2.插入排序

一.基本思想

二.插入排序的实现

复杂度

稳定性的分析

3.希尔排序

一.预排序代码的实现 

二.希尔排序代码实现

复杂度分析

4.clock函数


1.排序

一.概念及其分类

说到排序,我们都不陌生,一些基本的排序,比如冒泡,堆排等等

排序的概念呢则是:排序就是将一组杂乱无章的数据按照一定的规律(升序或降序)组织起来。

常见的排序算法

  • 加入排序
  • a. 直接插入排序
    b. 希尔排序
  • 选择排序
    a. 选择排序
    b. 堆排序
  • 交换排序
    a. 冒泡排序
    b. 快速排序
  • 归并排序
    a. 归并排序

二.排序的稳定性

稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。

简单来说就是,两个数字AB(A在B的前边),如果经过排序代码后,A仍就在B的前边,那么这个排序就是稳定的,反之不稳定。

2.插入排序

一.基本思想

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想

二.插入排序的实现

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与 array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

首先构造函数

void InsertSort(int *a,int n);

比方说我们现在有五个数字,现在进行一个插入排序

那么我们将end+1的数字定义为tmp

  • 当tmp大于end时,tmp就往前到end的前面
  • 然后end-1,再与tmp进行比较,知道tmp变成相比较之下更大的数字

这里还有另一种情况

当我们的tmp比前边的都要小时,如果不及时制止

那么就会出现越界的情况

这样我们的tmp就越界了,运行后就会崩

所以如果我们只考虑实现单趟的插入排序代码:

void InsertSort(int* a, int n)
{int end;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}a[end + 1] = tmp;}
}

这里跳出循环有两种情况

  • 1.找到了所需的插入位置:当tmp大于a[end]时,这是就会跳出循环

  • 2.tmp小于前边的所以数:当tmp在是数中最小的数字时,这是tmp就会不断地往前移动,直到成为整个序列的起始位

在这两种跳出循环的情况下,我们总是需要执行a[end+1]=tmp来将tmp元素放置到正确的位置上。因为无论是找到合适的插入点还是tmp成为新的最小元素,我们都需要将它实际插入到有序序列中,这就是为什么这行代码放在循环之外,确保跳出循环后,我们执行最终的插入动作。

接下来我们去考虑整体排序

因为上边的代码是单趟的排序

如果想要整体排序,那么就需要进行一个循环去实现整体的排序

void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

在这里我们进行了一个for循环,并且将end赋值为i

这样就相当于从一开始的两个数进行排序,当i的值不断增大,排序也就不断进行,同时其范围也在不断进行

注意这里的i需要小于n-1,不能是n

因为当i小于n的时候,这是end也就是n,而end+1则越界了,程序会崩

需要注意范围

测试一下

 这样就测试完成了

接下来看一看时间复杂度

复杂度

插入排序算法的时间复杂度取决于输入数组中元素的初始排序状态:

  • 最坏情况 :这时数组是完全逆序的,那么每次插入操作都需要将元素移到已排序部分的开头。这就意味着对于第i个元素,可能需要进行i次比较和移动。这种情况下,算法的时间复杂度是O(N2),因为需要进行总计1 + 2 + 3 + … + (n-1)次比较,这是一个n(n-1)/2的等差数列
  • 最好情况 :这种情况发生在数组已经完全有序时。在这种情况下,每次比较后,很快就会找到插入位置(在已排序元素的末尾),不需要进行额外的移动。因此,最好情况下插入排序的时间复杂度是O(N),因为外层循环只会遍历一次数组,内层循环不会进行任何实际的比较和移动操作。
  • 插入排序的空间复杂度为O(1),因为它是一个原地排序算法,不需要额外的存储空间来排序。

稳定性的分析

  1. 排序初始时,认为第一个元素自成一个已排序的序列

  2. 从第二个元素开始,取出未排序的下一个元素,在已排序的序列中从后向前扫描

  3. 如果当前扫描到的元素大于新元素(待插入),那么将扫描到的元素向后移动一个位置

  4. 重复步骤3,直到找到一个元素小于或等于新元素的位置,或者序列已经扫描完毕
    将新元素插入到这个位置后面

  5. 在步骤4中,插入排序的算法逻辑保证了如果存在相等的元素,新元素(待插入)将被放置在相等元素的后面。因此,原始顺序得以保持,插入排序被认为是稳定的

3.希尔排序

希尔排序是一种基于插入排序的算法,通过引入增量的概念来改进插入排序的性能

所以希尔排序是具有一定的优势的

希尔排序的基本思想是将原始列表分成多个子列表,先对每个子列表进行插入排序,然后逐渐减少子列表的数量,使整个列表趋向于部分有序,**最后当整个列表作为一个子列表进行插入排序时,由于已经部分有序,所以排序效率高。**这个过程中,每次排序的子列表是通过选择不同的“增量”来确定的。

实现思路:

  1. 预排序
  2. 整体直接插入排序

预排序:
根据当前增量,数组被分为若干子序列,这些子序列的元素在原数组中间隔着固定的增量。对每个子序列应用插入排序。

我们假设现在的增量是三

这样的话就会形成三组

  • 9 6 3 0
  • 8 5 2
  • 7 4 1

然后我们对着三组数据进行有序排序就会形成

  • 0 3 6 9
  • 2 5 8 
  • 1 4 7

然后我们将排列完的数据放回到原来的数组中就变成了

此时我们完成了第一轮的希尔排序

但现在的数据仍旧是乱的,但是相较于之前,已经变得有序了许多,然后减小增量,通常是将原来的增量除以2(如果增量序列选择为原始的版本)

但由于3无法整除2,所以我们这里直接取一进行排序

最后就会变成0 1 2 3 4 5 6 7 8 9 这样的序列

一.预排序代码的实现 

首先我们先进行单趟的控制

void ShellSort(int* a, int n)
{int gap = 3;int end;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = tmp;
}

这样我们就完成了单趟的排序

与上边插入排序不同的是我们这里均加减的是gap,也就是间隔增量数

这样单插完后

gap = 3为例,我们在进行控制这一组的子序列的整个过程

//希尔排序
void ShellSort(int* a, int n)
{int gap = 3;for (int i = 0; i < n - gap; i += gap){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;}
}

这里我们将定义的gap放到循环外

定义一个for循环,然后这里的i也是小于n-gap,防止越界,同时i也是+=gap,使得一个组的进行排序

在里面,我们定义end为i,这样也就和上边的代码一样了,只是加了一个for循环

然后再对整个序列进行排序

void ShellSort(int* a, int n)
{int gap = 3;for (int 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;}
}

这里,我们还是定义的gap为三,然后套用for循环,但这里的i增加是不断地加一而不是加gap

意味着将这所有的数分组后,进行一次排序完每个组的第二个元素,再进行下一个元素的排序

这里测试一下

说明我们的代码是成功的

二.希尔排序代码实现

我们对预排序的增量进行分析一下

我们将会发现一个规律

  • gap越大,大的值更快调到后面,小的值更快调到前面,越不接近有序
  • gap越小,大的值更慢调到后面,小的值更慢调到前面,越接近有序

所以,如果当我们的gap等于一时,我们的排序将会百分百的成为有序

所以,这里我们gap不可以是固定值,变成灵活变化的值将会更适合希尔排序

因此,在希尔排序的时候,我们将gap设置成随n变化而改变的值,从而实现多次排序

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 2;for (int 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;}}
}

 这里我们将gap设置为n,然后建立一个while循环,当gap>1时就可以进入循环进行分组排序,完成一次就将gap/2,然后不断重复,直到gap变成1,此时也会跳出循环。

这样就可以实现希尔排序

测试一下就是这样的

但这里有人提出了将gap/3会更好

因为在这里如果除2的话,预排序会很多,但很多时候经过预排序后已经很接近有序了

所以我们将2改为3

为了让最后的结果为1,我们也进行一些处理

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int 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;}}

测试一下

 这样也是ok的

复杂度分析

希尔排序的时间复杂度并不固定,它依赖于所选择的间隔序列(增量序列)。直到今天,已经有多种不同的间隔序列被提出来,每种都有自己的性能特点

所以很多不同的教科书给出了不同的定义

其稳定性则是不稳定

4.clock函数

这个函数是<time.h>头文件中的一个函数,用来返回程序启动到函数调用时之间的CPU时钟周期数。这个值通常用来帮助衡量程序或程序的某个部分的性能

我们可以用这个函数进一步对比两种排序占用的CPU时间

void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);free(a1);free(a2);
}

我们这里给100000个数据进行分析他们的时间 

发现希尔排序还是快的,相差了几十倍

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

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

相关文章

设计数据库之概念模式:E-R模型

Chapter3&#xff1a;设计数据库之概念模式&#xff1a;E-R模型 笔记来源&#xff1a;《漫画数据库》—科学出版社 设计数据库的步骤&#xff1a; 概念模式 概念模式(conceptual schema)是指将现实世界模型化的阶段进而&#xff0c;是确定数据库理论结构的阶段。 概念模式的设…

Ubuntu 安装 Carla仿真环境

1、系统要求 Ubuntu 16.04/18.04/20.04 CARLA 为 16.04 之前的 Ubuntu 版本提供支持。然而&#xff0c;Unreal Engine需要合适的编译器才能正常工作。 CARLA 服务器至少需要 6 GB GPU&#xff0c;但建议使用 8 GB。 2、安装NIVDIA驱动 BISO设置 开机F12&#xff0c;进入BIOS…

29-中断管理

中断管理 什么是中断管理&#xff1f; 用户可以自定义配置系统可管理的最高中断优先级的宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY&#xff08;高四位有效&#xff09;&#xff0c;它是用于配置内核中的basepri 寄存器的&#xff0c;当 basepri 设置为某个值的时候…

org.springframework.boot:type=Admin,name=SpringApplication异常

javax.management.InstanceNotFoundException: org.springframework.boot:typeAdmin,nameSpringApplication 问题描述&#xff1a; IDEA 新建 SpringBoot 项目&#xff0c;启动时后台报错&#xff1a; javax.management.InstanceNotFoundException: org.springframework.boot…

基于springboot的牙科就诊管理系统

技术&#xff1a;springbootmysqlvue 一、系统背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样…

粤嵌6818开发板如何理解Linux文件IO?

一、文件IO的概述 1、什么是文件&#xff1f; Linux下一切皆文件。普通文件、目录文件、管道文件、套接字文件、链接文件、字符设备文件、块设备文件。 2、什么是IO&#xff1f; input output&#xff1a;输入输出 3、什么是文件IO&#xff1f; 对文件的输入输出&#xff0c;把…

轻松找回丢失数据EasyRecovery数据恢复软件让你无后顾之忧

轻松找回丢失数据&#xff0c;EasyRecovery数据恢复软件让你无后顾之忧&#xff0c;电脑数码行业必备工具&#xff0c;让你的数据安全有保障 &#xff01; 一、EasyRecovery数据恢复软件简介 在我们的日常生活中&#xff0c;无论是工作还是学习&#xff0c;都离不开电脑和数据…

Linux/Perfection

Enumeration nmap 用 nmap 扫描了常见的端口&#xff0c;发现对外开放了 22,80&#xff0c;扫描一下详细信息&#xff0c;如下所示 ┌──(kali㉿kali)-[~/vegetable/HTB/Perfection] └─$ nmap -sC -sV -p 22,80 10.10.11.253 -oA nmap Starting Nmap 7.93 ( https://nmap…

目标检测——DOTA航拍数据集

DOTA数据集是一个用于航空图像中目标检测的大规模数据集&#xff0c;旨在帮助研究人员开发和评估航空图像中的目标检测算法。该数据集具有广泛的应用价值&#xff0c;尤其在计算机视觉和遥感技术领域。 DOTA数据集的特点主要体现在以下几个方面&#xff1a;首先&#xff0c;其…

408学习笔记-14-C-数据在内存中的存储

1、整数型存储 整数型存储就是所有整型家族里的数据类型的存储方式&#xff0c;也就是说包含了字符类型的存储&#xff08;因为字符的操作符的返回值是ASCII码值&#xff0c;故实际上存储的是整数&#xff09;。 1.1、有符号整数 有符号整数包含char&#xff0c;short&#x…

如何做接口测试?

今天来聊聊接口测试&#xff0c;现在是2024年了&#xff0c;打开招聘网站随便点开一个招聘帖子&#xff0c;几乎都可以看到岗位JD要求写着有接口测试经验优先。其重要性可见一斑&#xff01; 目前&#xff0c;凡是好一点稍具规模的公司哪怕是大厂外包也几乎都要求会接口测试&a…

QT(6.5) cmake构建C++编程,调用python

一、注意事项 explicit c中&#xff0c;一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)&#xff0c;承担了两个角色&#xff0c;构造器、类型转换操作符&#xff0c; c提供关键字explicit&#xff0c;阻止转换构造函数进行的隐式转换的发生&#…

使用Docker搭建YesPlayMusic网易云音乐播放器并发布至公网访问

目录 ⛳️推荐 1. 安装Docker 2. 本地安装部署YesPlayMusic 3. 部署公有云YesPlayMusic播放器 3.1 安装cpolar内网穿透 3.2 固定YesPlayMusic公网地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一…

【区间、栈】算法例题

目录 六、区间 48. 汇总区间 ① 49. 合并区间 ② 50. 插入区间 ② 51. 用最少数量的箭引爆气球 ② 七、栈 52. 有效的括号 ① 53. 简化路径 ② 54. 最小栈 ② 55. 逆波兰表达式求值 ② √- 56. 基本计算器 ③ 六、区间 48. 汇总区间 ① 给定一个 无重复元素 的 …

LeetCode # 199. 二叉树的右视图

199. 二叉树的右视图 题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3…

大规模电商平台数据采集难点分析♫

▁▃▅▇主要包括以下几方面&#xff1a; API工具 ◆◆数据量巨大 任何系统&#xff0c;在不同的数据量面前&#xff0c;需要的技术难度都是完全不同的。 如果单纯是将数据采到&#xff0c;可能还比较好完成&#xff0c;但采集之后还需要处理&#xff0c;因为必须考虑数据的规…

如何根据业务需求选择合适的电子合同平台?

在数字化转型的浪潮中&#xff0c;电子合同已经成为企业运营中不可或缺的一部分。然而&#xff0c;面对市场上众多的电子合同平台&#xff0c;企业如何根据自身的业务需求做出合适的选择呢&#xff1f;本文将为您一一解答。 在电子合同的签署过程中&#xff0c;数字证书颁发机…

鸿蒙Harmony应用开发—ArkTS-全局UI方法(日历选择器弹窗)

点击日期弹出日历选择器弹窗&#xff0c;可选择弹窗内任意日期。 说明&#xff1a; 该组件从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块功能依赖UI的执行上下文&#xff0c;不可在UI上下文不明确的地方使用&…

nodejs 常用命令

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;常用于服务器端编程。以下是一些 Node.js 的常用命令 1、安装 Node.js&#xff1a; 通常&#xff0c;你会从 Node.js 的官方网站下载并安装适合你操作系统的版本。安装完成后&#xff0c;你可以在命令行中…

跨域以及跨域配置

1、什么跨域 首先&#xff0c;在前后端没有分离项目&#xff0c;例如jsp、php&#xff0c;前后台代码没有实现物理上的分离。不存在跨域问题。前后端分离后&#xff0c;前后端的地址域名不同&#xff0c;而同源策略导致浏览器会拦截a地址访问b地址请求&#xff08;a地址通过浏览…