【数据结构】插入排序、希尔排序、冒泡排序、选择排序

news2024/11/18 9:21:39

文章目录

一、直接插入排序

思想

程序代码

时间复杂度

二、希尔排序

思想

程序代码

时间复杂度

三、冒泡排序

思想

程序代码

时间复杂度

四、选择排序

思想

程序代码

时间复杂度


一、直接插入排序

思想

        直接插入排序有些类似于我们玩扑克牌时的整理牌序动作,比如有整理好的手牌是 3,3,3,4,6,7,8,9,10,J,J,K;还差一张手牌 5 没有整理好,那么一般而言是会把 5 放在 4 的后面。而直接插入排序也类似,在一个有序序列中插入若干个数字,使最后的序列也是有序的。可以使用下图的做法:

        这里可能会比较疑惑,为什么不从左边开始比较,而从右边开始比较?一方面,从左或者从右比较两者没有什么差别,只是在上图的具体例子中,从左边插入确实比较的次数要少一些。另一方面,如果从左边插入,就只能找到 “3” 该在的位置,然后一次将后面的数据全部移走,这样子写代码比较麻烦,不如比较一次,移动一个数据

       那么如果给一个乱序的序列,如何将其排好序呢?比如[ 5,1,6,2,9,4,5,7,10 ] ,其实也是类似的原理,只需要把第一个数据看作是有序的(因为它本来就只有一个数据),然后将第二个数据按上面的算法插入进去,这样前两个数据就有序。然后将第三个数据插入……直到最后,所有数据就都有序了。

程序代码

        如下,只需要套一层循环,用 i 来控制,将第1个数据看作是有序的,从第二个数据开始插入,每一次插入一个数据。因为有 n 个数据,所以下标最多是n-1 。

void InsertSort(int* p, int n)
{
	for (int i = 0;i < n - 1;i++)
	{
		int end = i;
		int temp = p[i + 1];
		while (end >= 0)
		{
			if (p[end] > temp)
			{
				p[end + 1] = p[end];
				end--;
			}
			else
				break;
		}
		p[end + 1] = temp;//这一句不能写在else里面,是因为如果end=-1,那么不会进入while循环,也就白比较和后移了
	}                     
}

时间复杂度

        直接插入排序的时间复杂度还是很好分析的,按最坏的情况来看,一轮排序中,每次都要和所有数据比较完才停下来。比如有[  3,4,5,6,8] ,然后本次要插入的是数据 "2" ,需要比较到从右往左最后一个数据 “3” ,才发现也不满足,此时由于end 小于0,才跳出循环。

        这样子导致每次都要比较 i 次,所以时间复杂度= 1+2+3+……+ N = (N*N+N)/ 2,所以时间复杂度为O(N^2)。

二、希尔排序

        上面说到,直接插入排序可能会面临最坏的情况,就是原本升序的数据,要排成降序,这样每一个数据都要移动最多次。为了优化这个问题,D.L.Shell 于1959年发明了希尔排序。

思想

        此排序算法主要是将无序数组分割为若干个子序列,子序列不是逐段分割的,而是相隔特定的增量(记为gap)的子序列,对各个子序列进行插入排序然后再选择一个更小的增量,再将数组分割为多个子序列进行排序......最后选择增量为1,即使用直接插入排序,使最终数组成为有序

        如下图,假设此时 gap 为3,也就是下标间隔为3的数据是一个子序列。当进行第一组排序时,如图红色数据就是第一组,使用直接插入排序的算法。进行第二组排序时,第一组的数据已经排好了,下图也可以看出,然后进行蓝色的第二组数据排序。进行第三组排序时,前两组都排好了,进行绿色的第三组数据排序,得到最后的结果。

        最后得出的数据,虽然不是一个有序序列,但是相较之前的数据,已经相对有序了:值大的已经相对而言移到后面了,小的相对而言移到前面,这样子最后使用直接插入算法升序排序的时候,就会大大减少出现最坏的情况

        然后再对该组数据,进行 gap=1 的同样操作,gap=1就是直接插入排序,此时的数据相较于之前的数据,已经好排序多了,数据要移动的次数大大减少,时间复杂度得到了优化。

        但是此时,我们能不能把这个方法优化一下呢?这样一组一组比过去是否过于麻烦?我们可以化整为零,将一组一组分散成一对一对。如下图,gap也是39和5,1和7,2和4,分别是上面三组的第一次比较内容,这里我们按顺序第一次、第二次、第三次分别排序这三对数据,而不是之前一组一组来。

        三次完成之后,到了第四次,即原本第一组数据第二对数据 9和8 ,在之前的方法里,这对数据应该是第二次就排序了,但是在这里是第四次。
        再后面的某一次,也会到原本第一组数据的最后一对,如下图第三行(这里不仔细画出其中间排序的结果)。这样就将原本集中排序的第一组数据,分散为多次排序,每次排一对数据。另外两组也是同理。

        优化后的排序算法,其代码如下,可以对比上文的直接插入排序算法进行更好的理解,其实就是原本的gap=1,变成了gap=3 :

        gap = 3;
		for (int i = 0;i < n - gap;i++)
		{
			int end = i;
			int temp = p[i + gap];
			while (end >= 0)
			{
				if (p[end] > temp)
				{
					p[end + gap] = p[end];
					end -= gap;
				}
				else
					break;
			}
			p[end + gap] = temp;

程序代码

        代码如下,gap是自己设置的,数据多的时候,可能一开始是一个比较大的数字,比如15,并不是gap=15 排完之后就立刻排 gap =1 的情况,要逐层来,比较好的方法就是 gap/=2。第一次gap=15,第二次gap=7,第三次为3,第四次为1,这样子第四次排完之后,就完成排序了。控制条件就是在最外面加一个while循环,gap=1 的情况排完序之后,就跳出循环了。

void ShellSort(int *p,int n)
{
	int gap=n;
	while (gap > 1)
	{
		gap /= 2;//这样的好处是gap最后可以得到1
		for (int i = 0;i < n - gap;i++)
		{
			int end = i;
			int temp = p[i + gap];
			while (end >= 0)
			{
				if (p[end] > temp)
				{
					p[end + gap] = p[end];
					end -= gap;
				}
				else
					break;
			}
			p[end + gap] = temp;
		}
	}
}

时间复杂度

        希尔排序时间复杂度约为O(N^1.3),这个推导过程属实比较繁琐(其实是目前还未掌握..),所以就先不写啦!!

三、冒泡排序

思想

        冒泡排序重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

        假设有N个数据,需要升序排序,那么就要冒泡N-1次,每一次都把当前序列的最大值放到最后的位置,最后剩下的那个数据不需要冒泡,因为它就是最小的。示意图如下:

程序代码

void BubbleSort(int* p, int n)
{
	for (int i = 0;i < n - 1;i++)//冒泡次数,假设有4个数据,实际上冒泡好三个数据,剩下的就是最小的
	{
		int exchange = 0; // 标记一下,如果本轮冒泡没有数据交换,就是排好序了,退出循环

		for (int j = 0;j < n - i - 1;j++) //看第一次冒泡,假设有4个数据,那么只需要到第三个即可,因为下面的逻辑是当前数据和下一个数据比较  
		{
			if (p[j] > p[j + 1])
			{
				Swap(&p[j], &p[j + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			return;
	}
	return;
}

时间复杂度

        假设有N个数据,需要冒泡N-1次,如果每次冒泡都是最坏的情况,需要交换数据的次数都是拉满,那么总共需要交换数据的次数是: (N-1) + (N-2) + (N-3) + (N-4) + …… + 1 。最后算出来的结果是:N*(N-1) / 2 ,所以时间复杂度为 O(N^2)。

四、选择排序

思想

        选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
        思想是,首先在未排序序列中找到最小(大)元素,分别存放到序列的起始位置。再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始位置。重复第二步,直到所有元素均排序完毕。

        但是,可不可以优化一下子算法呢?一次找两个如何?在未排序序列中,寻找到最大的和最小的数据,记下来,然后最小的和当前序列起始位置互换最大的和当前序列末尾位置互换。重复此过程,最后得到的也是一个升序序列。

        如下图,第一次交换最大和最小的两个数据之后,首位两端算是排好了,第二次就要对中间未排的数据进行排序,重复此过程直到排完。

        但是,由于是要交换两个数据,难免会出现如下状况,避免错误的机制就是,交换完最小的数据和当前序列首位的数据之后,改变记录最大的数据下标的变量,让它变成正确的。具体可见下面的代码。

程序代码

        如下代码:

void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;

	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; ++i)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}

			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}

		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
			maxi = mini;

		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

时间复杂度

        任何情况都是O(N^2) 包括有序或接近有序。因为要一个一个比过去的。

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

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

相关文章

目标检测中常见的神经网络组成层------Pytorch

物体检测中常见的神经网络组成层 文章目录物体检测中常见的神经网络组成层卷积层激活函数层池化层Dropout层全连接层常见的物体检测算法常用卷积层、池化层、全连接层、激活函数层、Dropout层。 卷积层 CNN–各层的介绍_Miracle Fan的博客-CSDN博客_cnn各层介绍 在pytorch中…

Python3入门基础(08)一个函数

Python3 函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。 函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0…

Windows 11 连接 hercules zOS Websphere MQ 配置

基本配置 zOS v1.10 基本配置可以先参考下面教程 ubuntu 编译 hercules 主机安装 z/OS 教程 zOS MQ 版本为 CSQ700 Windows 11 IBM MQ 版本为最新 9.x IBM MQ 官网有开发者版本可以免费下载&#xff0c;档名如下 mqadv_dev931_windows.zip (全名为 IBM MQ Advanced for Devel…

kafka — 2、基础环境搭建

前述 kafka的运行依赖于zooKeeper&#xff0c;所以在搭建kafka的环境之前需要搭建zookeeper环境。 zooKeeper&#xff1a; ZooKeeper是一个分布式协调服务&#xff0c;它的主要作用是为分布式系统提供一致性服务&#xff0c;可以保证数据在集群间的事务一致性&#xff0c;提供…

Volatile不保证原子性

目录 前言 原子性 代码测试 为什么出现数值丢失 如何解决 其它解决方法 字节码指令表 前言 通过前面对JMM的介绍&#xff0c;我们知道&#xff0c;各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后在写回到主内存中的。 这就可能存在一…

Java Iterator(迭代器)

Java Iterator&#xff08;迭代器&#xff09;不是一个集合&#xff0c;它是一种用于访问集合的方法&#xff0c;可用于迭代 ArrayList 和 HashSet 等集合。 Iterator 是 Java 迭代器最简单的实现&#xff0c;ListIterator 是 Collection API 中的接口&#xff0c; 它扩展了 I…

微信小程序和ros2进行通信

微信小程序和ros2进行通信环境配置ubuntu下安装ros2ubuntu安装mqtt库windows安装微信开发者工具主要功能模块介绍ROS2基础程序讲解微信小程序基础程序讲解编译及运行ros2程序编译及运行微信小程序主要事项ROS2做为一款优秀的机器人操作系统软件&#xff0c;其搭载了丰富的机器人…

深耕低代码领域20年,这个老牌低代码平台有多靠谱

编者按&#xff1a;市面上低代码平台多如牛毛&#xff0c;用户在选型低代码平台时该如何甄别&#xff1f;本文通过介绍20年的老品牌低代码平台的特性&#xff0c;展示了优秀第低代码平台的魅力。 关键词&#xff1a;老厂商&#xff0c;私有化部署&#xff0c;源码交付&#xf…

实现effect的stop和onStop功能

06_实现effect的stop和onStop功能 一、实现stop &#xff08;一&#xff09;单元测试 it(stop, () > {let dummy;const obj reactive({ prop: 1 });const runner effect(() > {dummy obj.prop;});obj.prop 2;expect(dummy).toBe(2);stop(runner);obj.prop 3;expe…

zookeeper学习笔记1(小D课堂)

win和linux双环境安装zookeeper 我们不点击这个download。 进行解压&#xff1a; 我们进入到conf目录。 我们给这个文件进行重命名。 接下来我们去打开它&#xff0c;去修改一下配置&#xff1a; 我们先去掉这部分注释。 去掉注释&#xff0c;一共就这些内容。 tickTime是我们的…

如何开通阿里云语音通知服务?

阿里云语音服务是阿里云为用户提供的一种通信服务的能力。支持快速发送语音通知服务。 安全级别更高&#xff0c;难窃取。支持大容量、高并发&#xff0c;稳定可靠。 一、如何开通阿里云语音服务&#xff1f; 注册阿里云平台账户&#xff1b;实名登记认证&#xff1b;阿里云语…

手把手教你 如何利用github搭建个人网站 无需服务器

目录 前言 准备工作 教程来啦&#xff01;&#xff01;&#xff01; 第一部分 第二部分 链接说明 前言 哈喽&#xff0c;大家好&#xff0c;我是木易巷。 今天给大家分享一下&#xff1a;如何使用GitHub创建自己的个人网站&#xff1f; 准备工作 需要使用GitHub&#xff0c;当…

【python】语法分析-化学分子式解析「编译原理」

题目 编写程序&#xff0c;计算化学分子式中元素的数目&#xff0c;并完成以下测试&#xff1a; atom_count(“He”) 1 atom_count(“H2”) 2 atom_count(“H2SO4”) 7 atom_count(“CH3COOH”) 8 atom_count(“NaCl”) 2 atom_count(“C60H60”) 120 参考语法 specie…

学习周报-20221230

文章目录一 如何设置字符集二 NFS配置文件父目录权限影响子目录三 对IP分组可以批量管理NFS客户端首先查看系统环境 [rootnfs-server ~]# cat /etc/redhat-release Red Hat Enterprise Linux release 8.7 (Ootpa) [rootnfs-server ~]# uname -r 4.18.0-425.3.1.el8.x86_64这是…

JAVA零基础小白学习免费教程day13-Collection数据结构

day13_JAVAOOP 课程目标 1. 【理解】集合的体系结构 2. 【掌握】Collection集合中常用的方法 3. 【理解】Iterator迭代器 4. 【掌握】增强for的使用 5. 【理解】List集合的特点 6. 【掌握】List集合中特有的方法 7. 【理解】LinkedList集合的特点 8. 【理解】LinkedList集合中…

解读YOLO v7的代码(二)训练数据的准备

在上一篇文章解读YOLO v7的代码(一)模型结构研究_gzroy的博客-CSDN博客&#xff0c;我对Yolo v7的模型结构进行了分析&#xff0c;那么这次我们将进一步研读代码的关键部分&#xff0c;学习是如何对模型进行训练的。 训练数据的准备是模型训练的关键&#xff0c;通常我们需要对…

保姆教程系列一、什么?Redis部署 so easy

系列文章目录 &#xff01;&#xff01;&#xff01;是的没错&#xff0c;胖友们&#xff0c;保姆教程系列又更新了&#xff01;&#xff01;&#xff01; 保姆教程系列一、Redis部署 so easy 保姆教程系列二、Redis高可用&#xff08;主从同步哨兵模式&#xff09; 保姆教程系…

(3)Qt中的变体数据类型(QVariant)

QVariant的使用 QVariant(变体数据类型)这个类很神奇&#xff0c;或者说方便。很多时候&#xff0c;需要几种不同的数据类型需要传递&#xff0c;如果用结构体&#xff0c;又不大方便&#xff0c;容器保存的也只是一种数据类型&#xff0c;而QVariant则可以统统搞定。QVariant …

ceph-mds文件系统操作指南

前言&#xff1a;ceph-mds文件系统操作&#xff0c;常规操作汇总&#xff0c;看这一篇就够了 一、文件系统简介 Ceph 文件系统 (CephFS) 是兼容 POSIX 标准的文件系统&#xff0c;在 Ceph 的分布式对象存储基础上构建&#xff0c;称为 RADOS&#xff08;可靠的自主分布式对象存…

用户级线程和内核级线程

线程的实现可以分为两类&#xff1a;用户级线程和内核级线程&#xff0c;后者又称为内核支持的线程或轻量级进程。在多线程操作系统中&#xff0c;各个系统的实现方式并不相同&#xff0c;在有的系统中实现了用户级线程&#xff0c;有的系统中实现了内核级线程。 用户级线程&am…