排序算法——希尔排序图文详解

news2024/11/24 14:57:31

文章目录

  • 希尔排序
    • 基本思想
    • 整体插入思想
      • 预排序
      • 结论
    • 代码实现
    • 实现代码
    • 直接插入排序与希尔排序的效率比较
        • 测试代码:
    • 时间复杂度

希尔排序

注1:本篇是基于对直接插入排序法的拓展,如果对直接插入法不了解,建议先看看直接插入排序

注2:本篇统一采用升序排序

基本思想

  • 希尔排序法又称缩小增量法。

  • 希尔排序其实是直接插入排序的改进。

  • 基本思想是先选定一个整数gap,把待排序文件中所有记录分成数组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后缩小gap,重复上述步骤,当gap == 1时,所有记录在统一组内已经排好序。

整体插入思想

  • 在直接插入排序中,我们知道最坏的情况是待排序列降序逆序的情况,如序列:8,7,6,5,4,3,2,1,这时时间复杂度为O(N2),显然效率不高
  • 而希尔排序的思想,就是先对待排序列进行预排序,使待排序列接近有序。我们知道,当待排序列接近有序时,直接插入排序法的时间复杂度接近O(N),效率很高,因此预排序过后,就使用直接插入排序法,从而提高了效率。

预排序

  • 预排序实际上也是直接插入排序,但是是将待排序列分成数组来排

  • 根据基本思想,规定间隔为gap的数为一组

  • 我们以数组{9,8,7,6,5,4,3,2,1},gap = 3为例:

    • 每gap为一组:

      在这里插入图片描述

    • 对第一组排序:

      在这里插入图片描述

    • 对第二组排序:

      在这里插入图片描述

    • 对第三组排序:

      在这里插入图片描述

  • 这时相较于最开始,待排序列更加接近于有序,此时我们不断缩小gap,不断预排序,直到最后gap == 1时最后使用一次直接插入排序(gap == 1时的直接插入排序实际上就是最原始的直接插入排序),使待排序列有序

  • 又例如:

    在这里插入图片描述

结论

  • 希尔排序实际上就是多组间隔为gap的预排序,gap由大到小
  • gap越大,大的数能越快到后面,小的数能越快到前面
  • gap越大,预排序之后待排序列越不接近于有序
  • gap越小,预排序之后待排序列越接近于有序
  • 当gap == 1时,预排序实际上就是对整个序列进行直接插入排序,排完后序列即有序
  • 因此,最后一次预排序,gap必须为1.

代码实现

  • 对每间隔gap的一组数据进行排序,本质上就是直接插入排序,故不作过多讲解

    int end;
    int temp = nums[end + gap];
    while (end >= 0)
    {
        if (temp < nums[end])
        {
            nums[end + gap] = nums[end];
            end -= gap;
        }
        else
            break;
    }
    nums[end + gap] = temp;
    
  • 对多组间隔为gap的数据进行预排序

    • 以这张图为例:

      在这里插入图片描述

    • 我们上面的步骤只是将间隔为pap的一组数据进行了排序,但待排序列不止一组间隔为gap的数据,因此我们要做到将所有间隔为gap的每组数据都进行排序。

    • 怎么实现呢?可能最容易想到的是分别将每组间隔为gap的数据进行排序,例如上面分别对第一组,第二组,第三组排序,但是这样做效率不高,且操作复杂。因此我们要换一种想法,即把间隔为gap的数据同时排序

    • 如图:

      在这里插入图片描述

    for (int i = 0; i < numsSize - gap; i++)
    {
        int end = i;
        int temp = nums[end + gap];
        while (end >= 0)
        {
            if (temp < nums[end])
            {
                nums[end + gap] = nums[end];
                end -= gap;
            }
            else
                break;
        }
        nums[end + gap] = temp;
    }
    
  • 最后还要不断缩小gap的值,直到gap == 1

    int gap = numsSize;
    while (gap > 1)
    {
        gap /= 2;	//不断缩小gap
        /*
        	也可以写成 gap = gap / 3 + 1;
        	总之,必须要保证最后一次gap == 1
        */
    
        for (int i = 0; i < numsSize - gap; i++)
        {
            int end = i;
            int temp = nums[end + gap];
            while (end >= 0)
            {
                if (temp < nums[end])
                {
                    nums[end + gap] = nums[end];
                    end -= gap;
                }
                else
                    break;
            }
            nums[end + gap] = temp;
        }
    }
    

实现代码

void ShellSort(int* nums, int numsSize)
{
	int gap = numsSize;
	while (gap > 1)
	{
		gap /= 2;
		for (int i = 0; i < numsSize - gap; i++)
		{
			int end = i;
			int temp = nums[end + gap];
			while (end >= 0)
			{
				if (temp < nums[end])
				{
					nums[end + gap] = nums[end];
					end -= gap;
				}
				else
					break;
			}
			nums[end + gap] = temp;
		}
	}
}

直接插入排序与希尔排序的效率比较

  • 看到希尔排序有三层循环,可能有小伙伴会疑惑希尔排序为什么会比直接插入排序快,这里我们先上测试代码,直观的来感受这两个排序算法之间的差距:

测试代码:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//直接插入排序
void InsertSort(int* nums, int numsSize)
{
	for (int i = 0; i < numsSize - 1; i++)
	{
		int end = i;
		int temp = nums[end + 1];
		while (end >= 0)
		{
			if (temp < nums[end])
			{
				nums[end + 1] = nums[end];
				end--;
			}
			else
				break;
		}
		nums[end + 1] = temp;
	}
}

//希尔排序
void ShellSort(int* nums, int numsSize)
{
	int gap = numsSize;
	while (gap > 1)
	{
		gap /= 2;
		for (int i = 0; i < numsSize - gap; i++)
		{
			int end = i;
			int temp = nums[end + gap];
			while (end >= 0)
			{
				if (temp < nums[end])
				{
					nums[end + gap] = nums[end];
					end -= gap;
				}
				else
					break;
			}
			nums[end + gap] = temp;
		}
	}
}

int main()
{
	srand((unsigned int)time(NULL));
	
   //创建两个大小为N的数组
	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];
	}
	
   /*
   	clock()函数可以记录当前时间
   	begin和end的差即排序算法运行的时间
   	注:时间的单位为毫秒(ms)
   */
	int beginl = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	printf("InsertSort:%d\n", end1 - beginl);
	printf("ShellSort:%d\n", end2 - begin2);

   //释放内存
	free(a1);
	free(a2);

	return 0;

}

测试结果:

在这里插入图片描述

在这里插入图片描述

  • 我们可以看到,当数据个数为十万个时,直接插入排序所需要的时间是的希尔排序的100多倍
  • 当数据个数为一百万个时,直接插入排序所需要的时间时希尔排序的2000倍、
  • 可见,数据越多,希尔排序的优势就越明显,节省点时间就越多

时间复杂度

  • 从上面的测试中,我们直观的感受到了相较于直接插入排序,希尔排序的优越性,那么具体的希尔排序的时间复杂度为多少呢?

  • 我们先来看最外层的循环:

    int gap = numsSize;
    while (gap > 1)
    {
        gap /= 2;
        …………
    }
    
    • 设最外层循环运行了x次,那么2x = numsSize,x = log2N,即最外层的时间复杂度为log2N
  • 再看里面两层循环:

    for (int i = 0; i < numsSize - gap; i++)
    {
        int end = i;
        int temp = nums[end + gap];
        while (end >= 0)
        {
            if (temp < nums[end])
            {
                nums[end + gap] = nums[end];
                end -= gap;
            }
            else
                break;
        }
        nums[end + gap] = temp;
    }
    
    • 当gap很大时,尽管有两层循环,但数据之间跳跃的很大,需要排序的次数很少,因此时间复杂度为O(N),例如这种情况:

      在这里插入图片描述

    • 当gap很小时,尽管有两层循环,但此时数据已经接近有序,需要排序的次数也很少,因此时间复杂度也为O(N)。

  • 综上,希尔排序的时间复杂度为O(NLog2N)

  • 也可以认为时间复杂度为O(N1.3)

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

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

相关文章

Learning C++ No.27 【布隆过滤器实战】

引言 北京时间&#xff1a;2023/5/31/22:02&#xff0c;昨天的计算机导论考试&#xff0c;三个字&#xff0c;哈哈哈&#xff0c;摆烂&#xff0c;大致题目都是一些基础知识&#xff0c;但是这些基础知识都是非常非常理论的知识&#xff0c;理论的我一点不会&#xff0c;像什么…

【自制C++深度学习框架】表达式层的设计思路

表达式层的设计思路 在深度学习框架中&#xff0c;Expression Layer&#xff08;表达式层&#xff09;是指一个通用的算子&#xff0c;其允许深度学习网络的不同层之间结合和嵌套&#xff0c;从而支持一些更复杂的操作&#xff0c;如分支之间的加减乘除&#xff08;elementAdd…

PyTorch 深度学习 || 专题二:PyTorch 实验框架的搭建

PyTorch 实验框架的搭建 1. PyTorch简介 PyTorch是由Meta AI(Facebook)人工智能研究小组开发的一种基于Lua编写的Torch库的Python实现的深度学习库&#xff0c;目前被广泛应用于学术界和工业界&#xff0c;PyTorch在API的设计上更加简洁、优雅和易懂。 1.1 PyTorch的发展 “…

Numpy---生成数组的方法、从现有数组中生成、生成固定范围的数组

1. 生成数组的方法 np.ones(shape, dtypeNone, orderC) 创建一个所有元素都为1的多维数组 参数说明: shape : 形状&#xff1b; dtypeNone: 元素类型&#xff1b; order &#xff1a; {‘C’&#xff0c;‘F’}&#xff0c;可选&#xff0c;默认值&#xff1a;C 是否在内…

BPMN2.0自动启动模拟流程

思路&#xff1a;BPMN的流程模拟启动&#xff0c;主要是通过生成令牌&#xff0c;并启动令牌模拟 流程模拟的开启需要关键性工具&#xff1a;bpmn-js-token-simulation&#xff0c;需要先行下载 注&#xff1a;BPMN2.0的流程模拟工具版本不同&#xff0c;启动方式也不一样&am…

Kafka某Topic的部分partition无法消费问题

今天同事反馈有个topic出现积压。于是上kfk管理平台查看该topic对应的group。发现6个分区中有2个不消费&#xff0c;另外4个消费也较慢&#xff0c;总体lag在增长。查看服务器日志&#xff0c;日志中有rebalance 12 retry 。。。Exception&#xff0c;之后改消费线程停止。 查…

chatgpt赋能python:Python实现数据匹配的方法

Python实现数据匹配的方法 在数据分析和处理中&#xff0c;经常需要将两组数据进行匹配。Python作为一门强大的编程语言&#xff0c;在数据匹配方面也有着其独特的优势。下面我们将介绍Python实现数据匹配的方法。 数据匹配 数据匹配通常指的是将两组数据根据某些特定的规则…

理解calico容器网络通信方案原理

0. 前言 Calico是k8s中常用的容器解决方案的插件&#xff0c;本文主要介绍BGP模式和IPIP模式是如何解决的&#xff0c;并详细了解其原理&#xff0c;并通过实验加深理解。 1. 介绍Calico Calico是属于纯3层的网络模型&#xff0c;每个容器都通过IP直接通信&#xff0c;中间通…

试验SurfaceFlinger 中Source Crop

在 SurfaceFlinger 中&#xff0c;Source Crop 是用于指定源图像的裁剪区域的一个概念。Source Crop 可以理解为是一个矩形区域&#xff0c;它定义了源图像中要被渲染到目标区域的部分。在 Android 中&#xff0c;Source Crop 通常用于实现屏幕分辨率适应和缩放等功能。 在 Sur…

【Java基础篇】逻辑控制练习题与猜数字游戏

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&…

2023_Python全栈工程师入门教程目录

2023_Python全栈工程师入门教程 该路线来自慕课课程,侵权则删,支持正版课程,课程地址为:https://class.imooc.com/sale/python2021 学习路线以三个项目推动,一步步夯实技术水平&#xff0c;打好Python开发基石 目录: 1.0 Python基础入门 2.0 Python语法进阶 3.0 Python数据…

windows系统典型漏洞分析

内存结构 缓冲区溢出漏洞 缓冲区溢出漏洞就是在向缓冲区写入数据时&#xff0c;由于没有做边界检查&#xff0c;导致写入缓冲区的数据超过预先分配的边界&#xff0c;从而使溢出数据覆盖在合法数据上而引起系统异常的一种现象。 ESP、EPB ESP&#xff1a;扩展栈指针&#xff08…

React.memo()、userMemo 、 userCallbank的区别及使用

本文是对以下课程的笔记输出&#xff0c;总结的比较简洁&#xff0c;若大家有不理解的地方&#xff0c;可以通过观看课程进行详细学习&#xff1b; React81_React.memo_哔哩哔哩_bilibili React76_useEffect简介_哔哩哔哩_bilibili React136_useMemo_哔哩哔哩_bilibili Rea…

直播录音时准备一副监听耳机,实现所听即所得,丁一号G800S上手

有些朋友在录视频还有开在线会议的时候&#xff0c;都会遇到一个奇怪的问题&#xff0c;就是自己用麦克风收音的时候&#xff0c;自己的耳机和别人的耳机听到的效果不一样&#xff0c;像是音色、清晰度不好&#xff0c;或者是缺少伴奏以及背景音嘈杂等&#xff0c;这时候我们就…

2023贵工程团体程序设计赛

A这是一道数学题&#xff1f; 道路有两边。 #include<bits/stdc.h> using namespace std; int main(){int n,m;cin>>n>>m;cout<<(n/m1)*2;return 0; } BCPA的团体赛 直接输出 。 #include <bits/stdc.h> using i64 long long; #define IOS…

Docker基本管理与网络以及数据管理

目录 一、Docker简介1、Docker简述2、什么是容器3、容器的优点4、Docker的logo及设计宗旨5、Docker与虚拟机的区别6、Docker的2个重要技术7、Docker三大核心概念 二、Docker的安装及管理1、安装Docker2、配置Docker加速器3、Docker镜像相关基础命令①搜索镜像②拉取镜像③查看镜…

Linux 配置Tomcat环境(二)

Linux 配置Tomcat环境 二、配置Tomcat1、创建一个Tomcat文件夹用于存放Tomcat压缩包2、把Tomcat压缩包传入服务器3、解压并启动Tomcat4、CentOS开放8080端口 二、配置Tomcat 1、创建一个Tomcat文件夹用于存放Tomcat压缩包 输入指令 cd /usr/local 进入到 usr/local 输入指令 …

[LsSDK][tool] ls_syscfg_gui2.0

文章目录 一、简介1.工具的目的2. 更新点下个更新 三、配置文件 一、简介 1.工具的目的 ① 可视化选择IO口功能。 ② 自由配置IO支持的功能。 ③ 适用各类MCU&#xff0c;方便移植和开发。 ④ 功能配置和裁剪&#xff08;选项-syscfg-待完成–需要适配keil语法有些麻烦&#…

Node.js: express + MySQL + Vue实现图片上传

前段时间用Node.js: express MySQL Vue element组件做了一个小项目&#xff0c;记录一下图片上传的实现。 将图片存入数据库有两种方法&#xff1a; 1&#xff0c;将图片以二进制流的方式存入数据库&#xff08;数据库搬家容易&#xff0c;比较安全&#xff0c;但数据库空间…

微服务实战项目-学成在线-媒资管理模块(有项目实战实现)

学成在线-媒资管理模块 1 模块需求分析 1.1 模块介绍 媒资管理系统是每个在线教育平台所必须具备的&#xff0c;查阅百度百科对它的定义如下&#xff1a; 媒体资源管理(Media Asset Management&#xff0c;MAM)系统是建立在多媒体、网络、数据库和数字存储等先进技术基础上…