c语言-希尔排序

目录

一、插入排序

1、插入排序的概念

2、插入排序的逻辑实现

 3、插入排序的实现

二、希尔排序

1、希尔排序概念

2、希尔排序逻辑实现

3、间隔值(gap)对排序的影响

 4、希尔排序的实现

三、插入排序与希尔排序性能对比测试

结语:


前言:

        希尔排序的核心思想就是插入排序,他是在插入排序的基础上进行优化得来的,因此要想明白希尔排序的逻辑首先要认识插入排序。

一、插入排序

1、插入排序的概念

        插入排序又名直接插入排序,其思想是把要插入的数值,插入到一个有序的序列中,从而得到一个新的有序序列。现实中就跟摸牌一样,每次摸一张牌都会把这张牌插入到手中牌堆的合适位置,从而让手里的牌变得有序。

2、插入排序的逻辑实现

        排序的目的是让数组中的元素变得有序,比如冒泡排序的逻辑是从前往后遍历、相邻元素进行比较从而达到排序的功能。而插入排序的逻辑是从数组的最后一个元素往前遍历数组,举例:现如今要插入一个元素2,如果遇到大于2的元素则该元素往后“挪动”一位,直到遇到小于2的元素将2插入到该元素的后一位。

        另一种情况就是当数组内的元素都比要插入的元素要大:

 3、插入排序的实现

        在明白插入排序的逻辑后,以下用代码的形式将插入排序实现,并且测试最终结果。

#include<stdio.h>void InsertSort(int* a, int n)//插入排序
{for (int i = 0; i < n - 1; i++){int end = i;//表示当前数组的最后一个元素的下标int temp = a[i + 1];//一般而言,把end后一个元素看作是要插入进行排序的元素//因此为了防止该元素被end的元素所覆盖,因此要先保存起来while (end >= 0)//end小于0说明数组内元素都大于该插入的元素{if (a[end] > temp)//大于插入元素的情况{a[end + 1] = a[end];//“挪动”end--;//遍历end}else//小于或等于插入元素的情况break;//直接跳出}a[end + 1] = temp;//不论大于还是小于插入元素,都把要插入的元素给到end位置的后一位}
}void PrintArr(int* a, int n)//打印数组
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Test_InsertSort()//插入排序测试函数
{int arr[] = { 66,8,6,1,2,4,3,9 };int sz = sizeof(arr) / sizeof(int);PrintArr(arr, sz);InsertSort(arr, sz);PrintArr(arr, sz);}int main()
{Test_InsertSort();return 0;
}

        其实在对数组进行插入排序时,都是在数组内进行操作的,实际上不存在“从外边来了一个元素”要插入这个数组,而是从数组的第二个元素开始,把第二个元素看成是“要插入”的元素进行插入排序的。示意图如下:

         运行结果:

二、希尔排序

1、希尔排序概念

        希尔排序是在插入排序的基础上进行优化而来的,因为插入排序如果是对一个比较有顺序的数组进行排升序,那么其时间复杂度是O(N),但是如果插入排序要把一个降序的数组排成升序,那么他的时间复杂度就等于O(N^2),而希尔排序优势就是在面对这种情况的时间复杂度依然可以做到O(N)。

        希尔排序法又称缩小增量法,他的具体步骤可以分成两步:1、预排序,就是把要排序的数组按照某个间隔值分成若干个小数组。2、然后对这若干个数组进行插入排序。其中,让间隔值不断的缩小,直到间隔值为1时,希尔排序=插入排序,只不过在这个过程中希尔排序已经让数组变得有序了,所以哪怕最后希尔排序的效率=插入排序效率,总体而言希尔排序的效率是比插入排序要高的。详细如下文。

2、希尔排序逻辑实现

        由于插入排序面对降序转升序的情况不好处理,因此介绍希尔排序的时候用一个降序的数组作为例子,现在要对一个降序的数组进行排升序,逻辑图如下:

        待到红、蓝、绿三个数组全部都排好之后,数组内顺序如下:

        可以发现此时数组里的元素并不是一个升序状态,但是比处理前更接近升序了,这时候再对其进行插入排序则可以完成对该数组的升序排序,而且时间消耗也不大。

        代码实现:

void ShellSort(int* a, int n)//希尔排序
{int gap = 3;//假设gap=3for (int j = 0; j < gap; j++)//控制小数组{for (int i = j; i < n - gap; i += gap)//对小数组进行排序{//插入排序的逻辑int end = i;int temp = a[end + gap];//注意小数组之间的间隔是gapwhile (end >= 0){if (a[end] > temp){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = temp;}}
}

        这里gap=3只能让该数组变得接近升序而不能排成升序,上文还提到了一个问题就是当gap=1时,希尔排序=插入排序,因此如果可以控制gap的值的变化,最后让gap=1就能完成对数组的升序排序了。那为什么gap=1时,希尔排序=插入排序呢,gap对排序的影响详细如下文。

3、间隔值(gap)对排序的影响

         为什么间隔值gap=1的时候希尔排序=插入排序,从上文假设当gap=3,发现小数组中的元素与元素之间相隔了2个元素,那么gap=1时,元素与元素之间相隔0个元素,此时再对这些”小数组“进行排序实则就是对整个数组进行插入排序。

        从上图可以得出,当gap=1时就是等价于插入排序的,那么gap=3、=5时会对数组里元素的顺序有什么影响吗,如下图所示:

        可以发现当gap=1时,排出来的就是一个升序数组。

        当gap=3时,排出来的结果接近升序数组。

        当gap=5时,排出来的结果反而没那么接近升序了。

结论:当gap的值越大,则越不接近升序,当gap的值越小则越接近升序。但是gap值越大有一个好处是可以把数组中数值较大的元素挪到数组的后面,较小的值放到前面。因为间隔越大,则一次移动的步长就大。

        但是gap一开始不能设置成1,不然希尔排序就没有意义了,希尔排序的优势是让gap>1的时候快速的把数组调成接近有序的,然后再用插入排序进行排序。

        因此gap的值的变化是一个从大到1的过程,可以用gap=gap/3+1来表示(gap初始值设置为该数组的元素个数),并且作为循环内部的表达式,这样一来gap的值就会不断的缩小至1,其中n表示数组的元素个数,+1的目的是保证让gap最小值为1,若不+1则会出现gap=0的情况,就会发生死循环了。

 4、希尔排序的实现

        在了解了希尔排序的逻辑以及gap的值对排序结果的影响后,用代码将其实现:

#include<stdio.h>void ShellSort(int* a, int n)//希尔排序
{int gap = n;//gap初始化为n,n表示数组内元素个数while (gap > 1){gap = gap / 3 + 1;//第一次循环先带着较大的gap去排序,后续gap的值慢慢变小,最后至1for (int j = 0; j < gap; j++)//控制小数组{for (int i = j; i < n - gap; i+=gap)//将小数组内的元素进行排序{//插入排序思想int end = i;int temp = a[end + gap];//这里小数组内的元素间隔是gap,而不是1while (end >= 0){if (a[end] > temp){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = temp;}}}
}void PrintArr(int* a, int n)//打印数组
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Test_ShellSort()//希尔排序测试函数
{int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(int);PrintArr(arr, sz);ShellSort(arr, sz);PrintArr(arr, sz);}int main()
{Test_ShellSort();return 0;
}

        运行结果:

三、插入排序与希尔排序性能对比测试

        从代码的结构来看,会感觉到希尔排序的写法很复杂,而且逻辑也不简单,那么看起来如此复杂的希尔排序的性能一定比插入排序的性能要好吗,以下用一个测试代码进行对他们的性能测试:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>void ShellSort(int* a, int n)//希尔排序
{int gap = n;//gap初始化为n,n表示数组内元素个数while (gap > 1){gap = gap / 3 + 1;//第一次循环先带着较大的gap去排序,后续gap的值慢慢变小,最后至1for (int j = 0; j < gap; j++)//控制小数组{for (int i = j; i < n - gap; i += gap)//将小数组内的元素进行排序{//插入排序思想int end = i;int temp = a[end + gap];//这里小数组内的元素间隔是gap,而不是1while (end >= 0){if (a[end] > temp){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = temp;}}}
}void InsertSort(int* a, int n)//插入排序
{for (int i = 0; i < n - 1; i++){int end = i;//表示当前数组的最后一个元素的下标int temp = a[i + 1];//一般而言,把end后一个元素看作是要插入进行排序的元素//因此为了防止该元素被end的元素所覆盖,因此要先保存起来while (end >= 0)//end小于0说明数组内元素都大于该插入的元素{if (a[end] > temp)//大于插入元素的情况{a[end + 1] = a[end];//“挪动”end--;//遍历end}else//小于或等于插入元素的情况break;//直接跳出}a[end + 1] = temp;//不论大于还是小于插入元素,都把要插入的元素给到end位置的后一位}
}//对比测试
void Contrast_test()
{int n = 100000;srand(time(0));int* n1 = (int*)malloc(sizeof(int) * n);int* n2 = (int*)malloc(sizeof(int) * n);int* n3 = (int*)malloc(sizeof(int) * n);int* n4 = (int*)malloc(sizeof(int) * n);int* n5 = (int*)malloc(sizeof(int) * n);for (int i = 0; i < n; i++)//构建一个100000个元素的数组,并且存放随机值{n1[i] = rand() % 10000;n2[i] = n1[i];n3[i] = n1[i];n4[i] = n1[i];n5[i] = n1[i];}//clock函数返回的是系统启动到调用该函数的时间,单位是毫秒,并存到变量中int start1 = clock();InsertSort(n1, n);int end1 = clock();int start2 = clock();ShellSort(n2, n);int end2 = clock();printf("Test_InsertSort:%d\n", end1 - start1);//两个变量相减从而得到排序所消耗的时间printf("Test_ShellSort:%d\n", end2 - start2);free(n1);//释放空间free(n2);free(n3);free(n4);free(n5);
}int main()
{Contrast_test();return 0;
}

        运行结果:

        从结果可以观察到,插入排序的速度要比希尔排序的速度慢很多,也可以证明插入排序的消耗确实比希尔排序要大。 

结语:

        以上就是关于希尔排序的全部介绍以及实现,在掌握了希尔排序后会发现对插入排序的理解更加深刻了,其实各种不同的排序都有其存在的价值,也都有其适合的场景。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!

(~ ̄▽ ̄)~

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

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

相关文章

HAL库常用函数汇总【不间断更新】

1&#xff0c;系统函数 HAL_Delay 原型&#xff1a;void HAL_Delay(uint32_t Delay); 作用&#xff1a;利用滴答定时器生成的系统延迟函数 参数&#xff1a; Delay&#xff1a;延迟时间&#xff0c;单位是ms 返回值&#xff1a;无 示例代码&#xff1a; HAL_Delay(50);//延迟50…

内置函数【MySQL】

文章目录 MySQL 内置函数日期和时间函数字符串函数数学函数信息函数参考资料 MySQL 内置函数 MySQL 的内置函数主要分为以下几种&#xff1a; 字符串函数&#xff1a;用于对字符串进行操作&#xff0c;如连接、截取、替换、反转、格式化等。数值函数&#xff1a;用于对数值进…

使用 Mybatis 的 TypeHandler 存取 Postgresql jsonb 类型

文章目录 使用 TypeHandler 存取 Postgresql jsonb 类型常见错误column "" is of type jsonb but expression is of type character varying 使用 TypeHandler 存取 Postgresql jsonb 类型 首先在数据库表中定义 jsonb 类型&#xff1a; create table tb_user_info…

Android Studio导入项目一直显示正在下载Gradle项目

如题&#xff0c;问题图类似如下&#xff1a; &#xff08;此图是解决以后截的&#xff0c;之前遇到问题没截图&#xff09; 解决方法 先找到你正在下载的gradle的版本是哪个 然后在链接中 ​​​​​​Gradle Distributions 找到你所对于gradle的版本&#xff0c;下载对应…

【攻防世界-misc】János-the-Ripper

1.下载并解压桌面 2.用记事本打开misc100&#xff0c;可以看见文件里面是有flag.txt文件的&#xff0c; 3.将文件复制到虚拟机kali中&#xff0c;使用命令&#xff1a;binwalk -e 桌面/misc100 4.解压完以后打开桌面&#xff0c;会出现一个分离后的文件夹&#xff0c;打开文件…

windows10 Arcgis pro3.0-3.1

我先安装的arcgis pro3.0&#xff0c;然后下载的3.1。 3.0里面有pro、help、sdk、还有一些补丁包根据个人情况安装。 3.1里面也是这些。 下载 正版试用最新的 ArcGIS Pro 21 天教程&#xff0c;仅需五步&#xff01;-地理信息云 (giscloud.com.cn) 1、安装windowsdesktop-…

Git删除临时分支

愿所有美好如期而遇 软件开发过程中&#xff0c;总有功能要添加进来&#xff0c;当我们有一个功能开发了一半的时候&#xff0c;产品经理说这个功能不需要了&#xff0c;尽管很无奈&#xff0c;但还是要删除&#xff0c;我开发到一半的分支如何删除呢&#xff1f; 所以需要使用…

第14关 快速定位业务服务慢的问题:利用 Ingress-Nginx 和日志查询实现高效故障排查

大家好&#xff0c;我是博哥爱运维。 有这样的一个生产场景&#xff0c;客户访问我们的服务请求超时或感觉很慢的时候&#xff0c;会向我们的客服反馈问题&#xff0c;这个时候&#xff0c;客服就会来找到我们运维让帮助排查下原因。 这里我们运维人员首先要对自己业务的整个…

记录Windows下安装redis的过程

开源博客项目Blog支持使用EasyCaching组件操作redis等缓存数据库&#xff0c;在继续学习开源博客项目Blog之前&#xff0c;准备先学习redis和EasyCaching组件的基本用法&#xff0c;本文记录在Windows下安装redis的过程。   虽然redis官网文档写着支持Linux、macOS、Windows等…

冒泡排序以及改进方案

冒泡排序以及改进方案 介绍&#xff1a; 冒泡排序属于一种典型的交换排序&#xff08;两两比较&#xff09;。冒泡排序就像是把一杯子里的气泡一个个往上冒一样。它不断比较相邻的元素&#xff0c;如果顺序不对就像水泡一样交换它们的位置&#xff0c;直到整个序列像水泡一样…

使用opencv实现更换证件照背景颜色

1 概述 生活中经常要用到各种要求的证件照电子版&#xff0c;红底&#xff0c;蓝底&#xff0c;白底等&#xff0c;大部分情况我们只有其中一种&#xff0c;本文通过opencv实现证件照背景的颜色替换。 1.1 opencv介绍 OpenCV&#xff08;Open Source Computer Vision Librar…

UI上传组件异步上传更改为同步

实现异步方法 JavaScript 异步 实现异步的五种实现方法_js异步-CSDN博客 这两种比较经常用。 因为上传组件是异步上传的通过Async和await配合使用可以上传完照片视频后返回的地址在继续走下去&#xff0c;而不是图片视频地址还未获取时就上传后端了。

java文件上传以及使用阿里云OSS

JavaWeb 文件上传本地存储阿里云OSS配置文件 yml配置文件 文件上传 前端页面三要素&#xff1a; 表单项type“file” 表单提交方式post 表单的enctype属性multipart/form-data 本地存储 保证上传的文件不重复 //获取原始文件名String originalFilename image.getOriginalFi…

计算机毕业设计 基于PHP的考研互助交流系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

机器学习笔记 - 基于百度飞桨PaddleSeg的人体分割

一、简述 虽然Segment Anything用于图像分割的通用大模型看起来很酷(飞桨也提供分割一切的模型),但是个人感觉落地应用的时候心里还是更倾向于飞桨这种场景式的,因为需要用到一些人体分割的需求,所以这里主要是对飞桨高性能图像分割开发套件进行了解和使用,但是暂时不训练…

day64 django中间件的复习使用

django中间件 django中间件是django的门户 1.请求来的时候需要先经过中间件才能达到真正的django后端 2.响应走的时候也需要经过中间件 ​ djangp自带七个中间件MIDDLEWARE [django.middleware.security.SecurityMiddleware,django.contrib.sessions.middleware.SessionMiddle…

解决Maven项目jar包下载失败的问题

文章目录 配置国内的Maven源引入正确的settings.xml文件重新下载jar包对后面要创建的新项目也统一配置仍然失败的解决办法 配置国内的Maven源 引入正确的settings.xml文件 如果该目录下的 settings.xml文件不存在或者错误&#xff0c;要创建一个 settings.xml文件并写入正确的…

Java 常用容器

目录 列表栈&#xff08;类&#xff09;队列(接口)setMap 列表 package com.czl;import java.util.ArrayList; import java.util.List; //AltEnter导入包 public class Main {public static void main(String[] args) throws Exception{List<Integer> list new ArrayLis…

这个变量要不要用volatile修饰呢?

正文 大家好&#xff0c;又见面了&#xff0c;我是bug菌~ 在嵌入式软件开发过程中&#xff0c;如果对volatile不熟&#xff0c;那可以你应该是个"假嵌入式程序员"&#xff0c;因为一个变量需不需要使用volatile考虑的场景挺多的&#xff0c;如果在某些场景下乱用&…

读像火箭科学家一样思考笔记12_实践与测试(下)

1. 舆论的火箭科学 1.1. 如果苹果违反了“即飞即测”原则&#xff0c;那苹果的iPhone就不会问世了 1.1.1. iPhone在其上市前的民意调查中相当失败 1.1.1.1. iPhone不可能获得太大市场份额&#xff0c;不可能。 1.1.1.1.1. 微软前CEO史蒂夫鲍尔默&#xff08;Steve Ballmer&…