【排序算法】排序算法介绍及插入排序 ( 直接插入排序 希尔排序 )

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:数据结构
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 1.排序的概念和运用
    • 1.1排序的概念:
    • 1.2.常见排序应用:
    • 2.常见的排序算法:
  • 2、直接插入排序
    • 2.1 排序思路
    • 2.2 代码实现
    • 2.3 特性及复杂度
  • 3、希尔排序
    • 3.1 排序思路
    • 3.2 代码实现
    • 3.3 特性及复杂度
  • 4.总结:

1.排序的概念和运用

1.1排序的概念:

排序:所谓排序,就是将一串数据,按照某种规律,或者以某种特性或关键字,将数据按照递增或者递减,将数据从无序转变为有序的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,则称之为稳定。例如 a[i]=a[j],且 a[i] 在a[j] 之前,而在排序后的序列中,a[i] 仍在a[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:内部排序也称为内排序,是指待排的记录全部在内存中完成排序的过程,是被排序的数据元素全部存放在计算机内存中的排序算法。
外部排序:外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。根据排序过程的要求,是不能在内外存之间移动数据的排序。

1.2.常见排序应用:

而排序其实在我们生活中运用十分广泛,例如在购物网站上选购一个电脑,可以通过综合、销量、评论数、新品价格来排序,如果对价格有需求,也可以按照价格升序,或者降序来排序,通过这种方式来买到心仪的商品。
在这里插入图片描述

不难发现,在我们的日常生活中排序算的使用随处可见,充斥着我们的各项生产活动,我们的考试排名、绩效考核、综测排名,包括CSDN的各种排名都广泛的使用到了许多种不尽相同的排序算法。
在这里插入图片描述

而究其根本,这些都需要排序算法来实现。那么在编程中有什么排序算法,它们的性能如何?如何模拟实现这些算法?

接下来的几篇博客中,我们会依次学习常见的排序算法,并进行对比,以便于我们更好的理解这些算法的使用方式、使用条件与适用场景等等。

2.常见的排序算法:

在这里插入图片描述
最常用的排序算法可以分为四大类:插入排序、选择排序、交换排序与归并排序。插入排序的代表算法有直接插入排序与希尔排序;选择排序的排序算法代表是选择排序与堆排序;交换排序中我们要熟识冒泡排序与快速排序;归并排序则主要是以归并排序算法为主,及一些由归并思想衍生出的排序算法。

下图是常见八大排序的比较:
请添加图片描述

2、直接插入排序

2.1 排序思路

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个 已经排好序的有序序列 中,直到所有的记录插入完为止,得到一个新的有序序列 。

我们的扑克牌就使用了插入排序的思想:
在这里插入图片描述

比如拿到牌之后,我们都会理牌,那么通常就会按照大小顺序,将牌理成整体有序,或者局部有序。

其实插入排序的过程就可以想象成之前我们学习 顺序表 的尾插 ,我们假定序列 array[] 只有 1 个数。假定每次都是插入一个元素,且插入的元素需要将这个 ”顺序表“ 构成有序,如果插入元素array[i]<arr[i−1] ,那么就至少需要向前调整 1次;如果array[i]≥array[i−1] 那么就 直接插入在 i下标处 即可。

动图演示插入排序过程:
在这里插入图片描述

2.2 代码实现

对于 单趟插入排序 ,假设 end 是上一次排序的最后一个位置,那么本次需要排序的元素为 tmp=a[end+1] 。

之后通过一个循环,如果a[end]>tmp ,说明 tmp 插入的位置在前面,所以把a[end+1]=a[end] ,将元素往后移 1 格,并将 end−− ,将索引向前调整;如果 a[end]<=tmp ,说明tmp 在当前位置:end+1 就可以直接插入,停止循环。

最后 end 停下来的位置永远是插入位置的前一个位置 ,比如看下图:
在这里插入图片描述

这是单趟,那么完整的就是n−1 趟,因为第一个元素是不用排的,第一趟其实就是排的序列的第二个元素了。每趟最终插入的位置为 end + 1 ,注意防止越界。

完整的多趟 实现步骤:
1.从第一个元素开始,该元素可以认为已经被排序。
2.取下一个元素作为 tmp,从已排序的元素序列从后向前扫描。
3.如果该元素大于 tmp,则将该元素移到下一位。
4.重复步骤 3,直到找到已排序元素中小于或等于 tmp 的元素。
5.将 temp 插入到该元素的后面,如果已排序所有元素都大于 tmp,则将 tmp 插入到下标为 0 的位置。
重复步骤 2 ~ 5,直至完成排序。

void InserSort(int* a, int n)
{
	//多趟控制
	int i = 0;
	for (i = 0; i < n - 1; i++)
	{
		//单趟控制
		int end = i ;
		int temp = a[end + 1];
		while (end >= 0)
		{
			//目标数小于其它数时,其它数就往后挪;大于则插入
			if (temp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = temp;
	//代码执行到此有两种情况:
	//1.待插入元素找到应插入位置(break跳出循环到此)
	//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
	}
}


2.3 特性及复杂度

插入排序的最坏的情况为原始序列为降序 。此时,时间复杂度最坏,为O(N^2)。
如果目的是升序,序列也正好是升序的话,为最好情况,这时时间复杂度为O(N) 。

取其最坏,最终时间复杂度:O(N^2)
插入排序并没有开辟额外空间,所以 空间复杂度:O(1)

特性:元素集合越接近有序,直接插入排序算法的时间效率越高。
时间复杂度:O(N^2) 。
空间复杂度:O(1) 。
稳定性:稳定。

3、希尔排序

3.1 排序思路

希尔排序也属于插入排序,但是和直接插入排序又略微不同。

概念:希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把所有待排序记录记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,即所有记录在统一组内排好序。

简单来说,意思是取一个整数,作为 间距gap ,对于每个元素,与其间隔为gap 的元素分成一个组,对每组排序 。不断调整gap,对每组进行不断排序,在 gap调整到 1 后进行插入排序,就可以将数据排成有序。而按照间距gap分组并排序被称为 预排序

所以可以归纳希尔排序的步骤就是 两点

  1. 预排序 -目标:让数组接近有序(分组插排)
  2. 直接插入排序

比如,一次完整希尔排序 就像下图:
在这里插入图片描述

3.2 代码实现

希尔排序属于插入排序,而它的思想其实是和直接插入排序差不多的,因为最后一次希尔排序为插入排序。希尔排序无非就是多了几组预排序的过程。所以它的代码和直接插入排序十分相似。

对于插入排序,我们其实就可以看做是 gap = 1的希尔排序,那么把插入排序一些数据做一下替换,就得出了单组希尔排序 :

// 对于一组
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;
        }
        else
        {
            break;
        }
    }
    a[end + gap] = tmp;
}

这里的gap不仅是每组中元素的间距,也是组数。上面代码只完成了单组,对于完整的一组需要在外面套上一层循环,需要循环gap次:

for (int j = 0; j < gap ;j++) 
{
	for (int i = j; i < n - gap; i += gap)
    {
        // ...
    }
}

我们发现,只需要把之前的挪动 1 位看成挪动 gap 位。

注意 对单组进行排序时的结束条件i < n - gap ,这里结束条件是这个的原因为最后一组的最后一个元素下标为 n - gap - 1 ,当元素进行插入时,不能越界

但这样写,就套了 三层循环 了,看起来比较繁琐,我们可以做出一些调整,上方代码为排 gap 组,每次排一组。我们可以改进为gap 组并排

for (int i = 0; i < n - gap; i++) // 注意这里从 i+= gap 变为 i++
{
    int end = i;
    int tmp = a[end + gap];

    while (end >= 0)
    {
        if (tmp < a[end])
        {
            a[end + gap] = a[end];
            end -= gap;
        }
        else
        {
            break;
        }
    }

    a[end + gap] = tmp;
}

这里就只有 两层循环 了,代码更加简洁,唯一改变的是i+=gap 变为i++

这里就相当于,每一次都是排其他组的数据,举个例子:当前 i = 0 ,那么此刻排的就是第一组,之后i++ ,i=1 ,那么此刻就是排的第二组 … 当循环结束,这gap 组数据也就都被排好了。这就是gap 组并排。

而 希尔排序的预排序的 gap 越大时,那么对于单组中的数据就可以更快地到后面(挪动间距为gap),但这时的数据并不接近有序;而 gap 越小时,数据跳动越慢,但是越来越接近有序(比如插入排序) 。综合这两点,所以我们一般进行希尔排序时,gap 是动态变化的,gap 的初值一般为数组长度 。之后每次除以一半或者除以三分之一。

比如每次除以三分之一:

while (gap > 1)//gap=1 已经排过了
{
    gap = gap / 3 + 1; // 或 gap /= 2;
    // gap 组并排
    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;
            }
            else
            {
                break;
            }
        }
        a[end + gap] = tmp;
    }
}

分析:

这里调整是用的gap=gap/3+1,为什么在这边+1 呢?原因是希尔排序最后 1 次为插入排序,所以最后 1 次gap=1 ,如果 gap 初值为 8,如果每次仅仅让 gap/=3 ,由于c语言中 / 是下取整,那么每次gap 就为 8 2 0 ,最后 1次为 0 ,无法完成希尔排序,所以要加上一个 1 ,这样就可以保证最后 1 次 gap=1 。

但是如果每次除以2的情况就不需要,因为每gap/=2 最终结果必定等于 1 ,可以通过举例子证明。

3.3 特性及复杂度

数据被分为 gap 组,每组有 N/gap 个数据。对于单组,最坏的挪动次数就是1+2+3+…+N/gap−1 ,是公差为 1 的等差数列。

一共有gap 组,那么对于一次预排序的总次数就为(1+2+3+…+N/gap−1)×gap ,但是这里gap 是不确定的。那对于每次预排序的时间复杂度,该怎么计算?
在这里插入图片描述
1.首先,根据代码分析
如果gap 每次除以 3 ,那么就大约进行 N / 3 / 3 / 3… / 3 = log 3 N次;
如果gap 每次除以 2 ,那么就大约进行 N / 2 / 2 / 2… / 2 = log 2N 次;

2.其次,假设gap 每次除以 3 ,一开始 N 很大,gap=N/3+1 ,将当前 gap 的取值带入上方对于一次预排序的次数的公式中,通过极限的思想, ( 1 + 2 + 3 + . . . + N ( N / 3 + 1 ) N\over(N / 3 + 1) (N/3+1)N − 1 ) × ( N / 3 + 1 ) ≈ N ,那么起始处, 预排总次数约为 N

快结束时,gap 很小,本应该是N^2,但因为此刻由于预排序的作用 ,数组几乎有序,几乎不需要排序,这时次数也约为 N

而中间的计算结果需要的数学知识比较难,所以暂时先就将起始两次的结果作为每次预排序的结果,时间复杂度为 O(N) 。

3.那么综合起来,实际上希尔排序的时间复杂度大约在 [ log 3N ∗ N , log2N ∗ N ]

这个量级之间 ,和我们之前学习过的堆排序的时间复杂度相近。

但是这只是我们的一些计算推导,实际上希尔排序真正的时间复杂度很难计算,上面的计算只是简单推导而已,里面其实涉及到了很多数学知识,所以我们参考一下一些著名的数据结构书籍中对于希尔排序时间复杂度的分析:

《数据结构(C语言版)》— 严蔚敏:
在这里插入图片描述
《数据结构-用面相对象方法与C++描述》— 殷人昆
在这里插入图片描述

而gap 是按照Knuth 大佬提出的方式取值的,Knuth大佬进行了大量的试验统计,得出最接近的结论,希尔排序的最终时间复杂度为 O(N^1.3)左右。但如果有一天能找到最佳的gap,说不定快速排序的位置就被希尔取代了(bushi)
在这里插入图片描述

而空间复杂度就很简单了,并没有开辟额外的空间,所以空间复杂度为 O(1) 。

特性:希尔排序是对直接插入排序的优化。
当 增量 > 1时都是预排序,目的是让数组更接近于有序。当增量为 1 时,数组已经接近有序了,再进行排序就能提高算法执行的时间效率。
时间复杂度:O(N^1.3) 。
空间复杂度:O(1) 。
稳定性:不稳定。

4.总结:

今天我们认识并学习了排序算法的相关概念,并且具体学习了插入排序算法中的直接插入排序和希尔排序。下一篇博客,我们将继续学习交换排序中的直接选择排序和堆排序。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于Warning: World-writable config file ‘/etc/mysql/my.cnf‘ is ignored

不知道那个大兄弟&#xff0c;更改了my.cnf的权限为 0777 登陆mysqll的时候提示&#xff1a;Warning: World-writable config file /etc/mysql/my.cnf is ignored 里面的配置被忽略了, my.cnf不起作用 如果不是安装在docker里面的话&#xff0c;直接 chmod 0644 /etc/mysql/…

Java每日一练(20230405)

目录 1. 地下城游戏 &#x1f31f;&#x1f31f;&#x1f31f; 2. 汇总区间 &#x1f31f;&#x1f31f; 3. 寻找旋转排序数组中的最小值 II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C…

Selenium被检测为爬虫,怎么屏蔽和绕过

01、Selenium 操作被屏蔽 使用selenium自动化网页时&#xff0c;有一定的概率会被目标网站识别&#xff0c;一旦被检测到&#xff0c;目标网站会拦截该客户端做出的网页操作。 比如淘宝和大众点评的登录页&#xff0c;当手工打开浏览器&#xff0c;输入用户名和密码时&#x…

Java SE 基础 (6) 第一个Java程序

开发环境已经搭建完毕&#xff0c;可以开发我们第一个Java程序了。 Java程序开发三步骤&#xff1a;编写、编译、运行。 编写Java源程序 public class HelloWord {public static void main(String[] args) {System.out.println("HelloWord!");} } 第一个 HelloWo…

蓝桥杯 路径

答案 import mathdef lcm(i,j):m math.gcd(i,j)return i*j//m n 2021 dp [float(inf)]*2022 dp[1] 0 for i in range(1,n1):for j in range(i1,i22):if j > n:breakdp[j] min(dp[j],dp[i]lcm(i,j)) print(dp[n]) 对dp[j] min(dp[j],dp[i]lcm(i,j))的解析&#xff1a;…

JAVASE 继承

文章目录继承1.为什么需要继承2.继承的概念3.继承的语法4.父类成员访问4.1 子类中访问父类的成员变量4.2 子类中访问父类的成员方法5 super关键字6.子类的构造方法7.super和this8.再谈初始化9.protected关键字10.继承方法11.final 关键字12.继承与组合继承 1.为什么需要继承 …

【C++笔试强训】第十天

选择题 解析&#xff1a;内联函数&#xff08;inline&#xff09;一般用于代码较少&#xff0c;代码块里面没有递归且频繁调用的函数&#xff0c;是一种以空间换时间&#xff08;不是指内存&#xff0c;而是指令变多编译出来的可执行程序会变大&#xff09;的做法。内联函数在预…

49天精通Java,第14天,Java泛型方法的定义和使用

目录一、基本介绍1、Java泛型的基本语法格式为&#xff1a;2、在使用泛型时&#xff0c;还需要注意以下几点&#xff1a;二、泛型的优点1、类型安全2、消除强制类型转换3、更高的效率4、潜在的性能收益三、常见泛型字母含义四、使用泛型时的注意事项五、泛型的使用1、泛型类2、…

第五章 Vite4+Vue3+Vtkjs 自定义按键组合

一、介绍 因为Vtk.js在按键和按键组合上默认就指定了对应的事件处理,但是我们在使用其他软件的时候可能已经养成了一种习惯,然后也希望使用Vtk.js的时候按键对应的事件也是一致的。比如右键是平移模型,或者说shift+鼠标右键是平移,不管是什么按键的组合,对应的事件是我们…

颠覆认知!“垃圾股”策略长期跑,10年翻100倍、近2年6倍,吊打茅指数!| 邢不行

这是一个非常简单的量化选股策略&#xff0c;它只用到了两个基础选股指标。 代表策略的橙色曲线2010年至今从1元涨到了112元&#xff0c;年化收益43%&#xff1b;在近两年大盘下跌的情况下&#xff0c;这个策略更是逆势翻了6倍。 这个量化策略究竟用了哪两个选股指标&#xf…

java TreeSet 和 TreeMap 源码解读

目录 一、前言 二、TreeSet详解 1.TreeSet简介 2.TreeSet的底层实现 0 准备工作 1 TreeSet构造器 2 匿名内部类实现接口的多态 3 TreeMap构造器 4 add方法 5 put方法和put方法 6 继续添加元素 7 修改比较器的比较原则 三、TreeMap详解 1.TreeMap简介 2.TreeMap的底层实现 0…

拥有良好的社交和友谊会使肠道微生物群更健康

谷禾健康 播种肠道&#xff0c;喂养心灵 在新冠疫情的影响下&#xff0c;我们的生活方式和社交模式都发生了很大的改变。随着社交距离的要求和封锁措施的实施&#xff0c;我们不得不放弃了很多与朋友和家人的互动&#xff0c;这给我们的身心健康带来了很大的影响。 然而&#x…

区块链学习笔记(3)BTC协议

假设有一个大家都信任的中心化机构想要发行数字货币。 该机构由用自己的私钥签名后后发行&#xff0c;任何人都可以通过公钥验证该货币是否为真。 买东西的时候&#xff0c;购买者可以将数字货币发送给卖方&#xff0c;卖方可以也可以通过公钥验证该货币为真后即可完成支付的过…

子网掩码和CIDR

CIDR是什么 网络标识相同的计算机必须同属于同一个链路。例如&#xff0c;架构B类IP网络时&#xff0c;理论上一个链路内允许6万5千多台计算机连接。然而&#xff0c;在实际网络架构当中&#xff0c;一般不会有在同一个链路上连接6万5千多台计算机的情况。因此&#xff0c;这种…

蓝桥杯刷题冲刺 | 倒计时7天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;最后一周&#xff0c;复习学过的知识&#xff0c;刷题冲刺&#x1f43e; 文章目录1.高精度除法2.扫地机器人3.数的范围4.A-B 数对1.高精度除法 题目 链接&#xff1a; 794. 高精度除法 - AcWing题…

Java对象内存布局

文章目录1、对象头对象标记Mark Word类元信息&#xff08;又叫类对象指针&#xff09;Class Pointer数组长度&#xff08;Array Length&#xff09;&#xff08;可选&#xff09;2、实例数据&#xff08;对象体&#xff09;3、对齐填充4、指针压缩5、再聊对象头的MarkWord6、JO…

Android ART虚拟机 Space类体系

前言 在ART虚拟机实现中&#xff0c;内存分配和释放的算法是封装在不同的Space中来完成的。而外部使用者只能借助Space及派生类的接口来完成内存的分配与释放。通过阅读这些Space的实现&#xff0c;可以看出ART虚拟机的一个重要的特点就是大量使用映射内存&#xff0c;相较于D…

思维导图软件哪个好?安利八款好用的思维导图软件

当你需要表达和整理复杂的想法、计划和项目时&#xff0c;思维导图软件可以是非常有用的工具。不同的思维导图软件有不同的功能和特点&#xff0c;选择适合自己的软件可以让你更高效地工作和学习。但是你了解思维导图软件哪个好呢&#xff1f;下面就给大家安利八款简单好用的思…

分享99个ASP影音娱乐源码,总有一款适合您

分享99个ASP影音娱乐源码&#xff0c;总有一款适合您 99个ASP影音娱乐源码下载链接&#xff1a;https://pan.baidu.com/s/1pYpAqFUX0xD8KR8GDRyiug?pwd3lja 提取码&#xff1a;3lja Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 我的博客地址&#xff1a;亚…

1Panel开源面板项目GitHub Star数量突破2,000!

截至2023年4月4日18:00&#xff0c;FIT2CLOUD飞致云旗下开源项目——1Panel开源Linux服务器运维管理面板GitHub Star数超过2,000个&#xff01;