数据结构——排序(1)

news2025/1/15 8:36:11

数据结构——排序(1)

文章目录

  • 数据结构——排序(1)
    • 一、排序
      • 1.概念:
          • 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
      • 2.运用:
          • 购物筛选排序,院校排名等。
      • 3.常见排序算法
    • 二、实现排序算法
      • 1.插入排序
        • 1.1直接插入排序
        • 1.2希尔排序
      • 2.选择排序
        • 2.1直接选择排序
        • 2.2堆排序
      • 3.交换排序
        • 3.1冒泡排序
        • 3.2快速排序(采用二叉树递归的思想)

一、排序

1.概念:

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

2.运用:

购物筛选排序,院校排名等。

3.常见排序算法

在这里插入图片描述

二、实现排序算法

1.插入排序

1.1直接插入排序

基本思想

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

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

动图理解:

在这里插入图片描述

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

代码实现:

void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;//有序区间的最后一个数据
		int tmp = arr[end + 1];//有序区间的后一个数据
		
		while (end >= 0)
		{
			if (arr[end] > tmp)//当arr[end]>tmp,两值交换
			{
				arr[end + 1] = arr[end];//把tmp放在end的位置
				end--;  //end移动到有序区间的倒数第二个数据
			}
			else     //当arr[end]<=tmp,不做处理,结束循环
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}
  • 为什么for循环的判断条件是i<n-1,而不是i<n呢?

因为当end=n-1时,tmp=arr[n],这里越界了,所以for循环的判断条件是i<n-1。

  • 时间复杂度

最差的情况(数组有序且为降序的情况下时间复杂度最差):时间复杂度为O(N2)

最好的情况(数组有序且为升序的情况下时间复杂度最差):时间复杂度为O(N)。

  • 空间复杂度:O(1)
1.2希尔排序

曾经我们学过冒泡排序,我们知道冒泡排序的算法效率极低(最差的时间复杂度为O(N2)),所以在实际工作中,我们不会用到它。即冒泡排序只有教学意义,没有实际意义。

直接插入排序有实际意义吗?

它有实际意义,但是要优化一下。当数组为降序序列时,直接插入排序还能得到优化吗?这里我们就要引出——希尔排序

希尔排序法⼜称缩小增量法,是一种改进的插入排序算法。它通过比较相距一定间隔的元素来进行排序,逐步缩小间隔,最终间隔为1时,算法退化为普通的插入排序。希尔排序的名称来源于其发明者Donald Shell。

基本思想:

希尔排序法的基本思想是:先选定⼀个整数(通常是gap = n/3+1),把待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进⾏排序,然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进⾏插⼊排序,当gap=1时,就相当于直接插⼊排序。

以排序数组为例(gap取2)

在这里插入图片描述

代码实现:

void ShellSort(int* arr, int n)
{
	int gap = n;//6//gap表示我们要分多少组
	while (gap > 1)
	{
		gap = gap / 3 + 1;//6除3=2,2除以3=0 //+1是为了保证最后一次gap一定为1
		for (int i = 0; i < n - gap; i ++)
		{
			int end = i;
			int tmp = arr[end + gap];//tmp最后一个取值要保证不越界
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

时间复杂度分析:

外层循环:

外层循环的时间复杂度可以直接给出为: O(log2 n) 或者 O(log3 n) ,即 O(log n)

内层循环:在这里插入图片描述

假设⼀共有n个数据,合计gap组,则每组为n/gap(大致)个;在每组中,插⼊移动的次数最坏的情况下为 S=1 + 2 + 3 + ……+ (n/gap-1),⼀共是gap组,因此:

总计最坏情况下移动总数为:gap ∗ S

gap取值有(以除3为例):n/3 n/9 n/27 … 2 1

一 一带入:

  • 当gap为n/3时,移动总数为: n

  • 当gap为n/9时,移动总数为: 4n

  • 最后⼀趟,数组已经已基本有序了,gap=1即直接插入排序,移动次数就是n

通过以上的分析,可以画出这样的曲线图:

在这里插入图片描述

因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程。

希尔排序时间复杂度不好计算,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。《数据结构(C语⾔版)》— 严蔚敏书中给出的时间复杂度为:在这里插入图片描述

总而言之,希尔排序的时间复杂度范围为:O(N1.3)~O(N2)

  • 希尔排序的时间性能优于直接插入排序的原因:

在希尔排序中,随着增量的减小,元素的移动次数会显著降低。当数据接近有序时,直接插入排序的性能显著提升,而希尔排序则更早地创建了部分有序的数据集合,使得后面的排序过程更加高效。

2.选择排序

2.1直接选择排序

基本思想:

每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

动图理解:

在这里插入图片描述

  1. 在元素集合 array[i]–array[n-1] 中选择关键码最⼤(⼩)的数据元素

  2. 若它不是这组元素中的最后⼀个(第⼀个)元素,则将它与这组元素中的最后⼀个(第⼀个)元素交换

  3. 在剩余的 array[i]–array[n-2](array[i+1]–array[n-1]) 集合中,重复上述步骤,直到集合剩余 1 个元素

代码实现:

void SelectSort(int* arr,int n)
{
    int begin=0;
    int end=n-1;
    
    while(begin<end)
    {
        int mini=begin,maxi=begin;
        for(int i=begin+1;i<=end;i++)
        {
            if(arr[i]>arr[maxi])
            {
                maxi=i;
            }
            if(arr[i]<arr[mini])
            {
                mini=i;
            }
        }
       //避免maxi begin都在同一个位置,begin和mini交换之后,mini数据变成了最小的数据
        if(maxi==begin)
        {
            maxi=mini;
        }
        Swap(&arr[mini],&arr[begin]);
        Swap(&arr[maxi],&arr[end]);
        
        ++begin;
        --end;
    }
}
  • 时间复杂度是O(n2).
2.2堆排序

堆排序是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀种。它是通过堆来进行选择数据。需要注意的是排升序要建⼤堆,排降序建小堆。在上篇二叉树(下)中我们已经实现过堆排序,这⾥不再细述。

3.交换排序

3.1冒泡排序

冒泡排序是⼀种最基础的交换排序。之所以叫做冒泡排序,因为每⼀个元素都可以像小气泡⼀样,根据自身大小⼀点⼀点向数组的⼀侧移动。

动图理解:

在这里插入图片描述

代码实现:

void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - i - 1; j++)
		{
			//升序
			if (arr[j] > arr[j + 1])
			{
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}
  • 时间复杂度(最差):O(N2).
3.2快速排序(采用二叉树递归的思想)

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法。

基本思想:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左⼦序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值mid
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,ley-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}
  • 空间复杂度:O(logn)
  • 时间复杂度:O(nlogn)

找基准值的三种方法

  • hoare版本

算法思路 :

1)创建左右指针,确定基准值;

2)从右向左找出⽐基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进⼊下次循环.

问题1:为什么跳出循环后right位置的值⼀定不⼤于key?

当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指向的数据⼀定不⼤于key

问题2:为什么left 和 right指定的数据和key值相等时也要交换?

相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量重复时,无法进⾏有效的分割排序。

int _QuickSort1(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left<=right)//left和right相遇的位置的值比基准值要大
	{
		while (left <= right && arr[right] > arr[keyi])
		{
			right--;
		}
		//right找到比基准值小或等于
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		} 
	}
	
	Swap(&arr[keyi], &arr[right]);
	return right;
}
  • 挖坑法

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

在这里插入图片描述

int _QuickSort2(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];

	while (left < right)
	{
		while (left<right && arr[right]>key)
		{
			--right;
		}
		arr[hole] = arr[right];
		hole = right;

		while (left < right && arr[left] < key)
		{
			++left;
		}
		arr[hole] = arr[left];
		hole = left;

	}
	arr[hole] = key;
	return hole;
}
  • lomuto前后指针法

    创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

在这里插入图片描述

int _QuickSort3(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;

	while (cur<=right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);

	return prev;
}

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

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

相关文章

业务的合并与分批

1.你中有我&#xff0c;我中有你 2.合久必分&#xff0c;分久必合 3.正负负正&#xff0c;正道无穷

MRI学习笔记-LItool使用教程,计算偏侧化指数lateralization index

偏侧化指数介绍 描述功能偏侧化最常用的方法之一是计算偏侧化指数&#xff0c;分别是左侧和右侧感兴趣区域&#xff08;ROI&#xff09;中值高于特定激活阈值的体素数。因此&#xff0c;LI值的范围从1&#xff08;左主导&#xff09;到1&#xff08;右主导&#xff09;。然而&…

AtCoder Beginner Contest 375 A-E 题解

我的老师让我先做最后再交&#xff0c;看正确率&#xff08;即以OI赛制打abc&#xff09; 所以我用的小号&#xff08;… …&#xff09; C 卡了老半天才出来&#xff0c;我把题读错了 难度&#xff1a; A. Seats 题意 给你一个字符串 S S S&#xff0c;仅包含 . 和 #&…

unity 调整skinweight (皮肤权重),解决:衣服穿模问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、skinweight 是什么&#xff1f;二、代码控制&#xff1a;可根据平台切换1.引入库 总结 前言 最近遇到一个问题&#xff0c;人物模型的衣服穿模&#xff08…

本地拉取Docker镜像打包导入远程服务器

起因是因为使用远程服务器拉取镜像时&#xff0c;由于网络问题一直拉不成功&#xff0c;使用国内镜像由于更新不及时&#xff0c;国内镜像没有最新的 docker 镜像。最后使用本地的计算机&#xff0c;通过代理下载最新的镜像后打包成 tar&#xff0c; 然后上传到远程服务器进行导…

interwirelessac9560感叹号,电脑无法连接wifi,无法搜索到wifi

interwirelessac9560感叹号 电脑无法连接wifi&#xff0c;无法搜索到wifi 原因 这可能是wifl模块出现了问题。 解决方案 1、winx 打开&#xff0c;选择【设备管理器】 2、选择网络适配器 右键打开wireless-AC&#xff0c;选择【卸载设备】。 3、关机2分钟后&#xff0c…

计算机网络-VRRP实验配置

前面我们大致学习了VRRP的概念和基本原理&#xff0c;但是网络这块就是要多敲命令多用才能印象深刻&#xff0c;今天开始进行一些实验配置&#xff0c;结合日常工作的场景分析VRRP在实际工作中的应用。 一、典型VRRP虚拟网关拓扑 相比于传统单网关&#xff0c;采用VRRP虚拟网关…

Forward Chaining(前向链推理)

这是一个 Forward Chaining&#xff08;前向链推理&#xff09; 的例子&#xff0c;用于在给定的命题逻辑规则集下&#xff0c;从已知的事实推导出新结论。图示右侧显示了推理过程的依赖关系图&#xff0c;左侧是规则和初始事实。 我们有以下命题逻辑规则和已知的事实&#xf…

几种常用大模型工具生成基于hi3861的OpenHarmony代码的尝试

引言 最近在上智能物联网的课程&#xff0c;讲授基于hi3861的OpenHarmony编程&#xff0c;所以尝试一下使用大模型工具生成相关的代码&#xff0c;看看效果如何。提问的方式比较简单粗暴&#xff1a; 在OpenHarmony的hi3861平台上&#xff0c;如何编程访问https的网站&#xf…

iOS 打包/导出时提示图标错误,缺少某个规格的图标

Asset validation failed Missing required icon file. The bundle does not contain an app icon for iPad of exactly ‘167x167’ pixels, in .png format for iOS versions supporting iPad Pro. To support older operating systems, the icon may be required in the bun…

唐寅,风流倜傥的艺术天才

唐寅&#xff0c;字伯虎&#xff0c;号六如居士&#xff0c;生于明宪宗成化六年&#xff08;公元1470年&#xff09;&#xff0c;卒于明世宗嘉靖二十三年&#xff08;公元1524年&#xff09;&#xff0c;享年54岁。他是吴门画派的重要代表人物之一&#xff0c;不仅在绘画方面有…

分享两种安装windows系统教程,学会后再也不需要花钱装系统了。

前期准备工作&#xff1a; 需要一个8G或16G的空U盘需要你安装的系统的镜像文件 一般是一个以 .iso 后缀结尾的文件 2.1 镜像文件获取方式 1&#xff09; 去windows 官网获取 2&#xff09;去 我告诉你 网址下载所需要的镜像文件 这个网址 分享了很多 我们常用的系统 大家可以按…

docker构建jar镜像

文章目录 构建 DockerFile将jar包上传到创建的目录当中在目录中创建 Dockerfile 文件构建镜像创建并启动容器说明 构建 DockerFile [root192 /]# mkdir my [root192 /]# cd my [root192 my]# 将jar包上传到创建的目录当中 在目录中创建 Dockerfile 文件 vi Dockerfile FROM …

RabbitMQ 入门(六)SpringAMQP五种消息类型

一、发布订阅-DirectExchange&#xff08;路由模式&#xff09; 在Fanout模式中&#xff0c;一条消息&#xff0c;会被所有订阅的队列都消费。但是&#xff0c;在某些场景下&#xff0c;我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。 Direct Exchan…

共识算法Raft(day11)

引入 在分布式系统中&#xff0c;为了消除单点提高系统可用性&#xff0c;通常会创建副本来进行容错&#xff0c;但这会带来另一个问题就是&#xff0c;如何保证多个副本之间的数据一致性。 为了解决这个问题&#xff0c;计算机行内就提出了共识算法&#xff0c;它允许多个分…

增强对象智能:谷歌开源的XR-Objects项目简介

随着增强现实(AR)技术的发展,将物理世界与数字信息融合的需求日益增长。为了探索这一领域的可能性,谷歌推出了一项名为“增强对象智能”(Augmented Object Intelligence, AOI)的新交互范式,并发布了一个开源原型系统——XR-Objects。该系统旨在通过实时对象分割和多模态…

SpringBoot智能推荐:健康生活新体验

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

行业分析---自动驾驶人才流动及从业思考

1 背景 近两年在自动驾驶行业&#xff0c;人才流动非常频繁&#xff0c;最新且影响比较大的例子是小鹏多位高管 “转会” 英伟达。自小鹏前自动驾驶副总裁吴新宙去年8月加入英伟达后&#xff0c;12个月里&#xff0c;至少6位小鹏技术人员加入英伟达。 如果把时间拉得更长一些&a…

MySql数据库---索引、引擎、事务

思维导图 索引 索引特点 通过某一列的特征.给表添加一个索引列.索引列需要mysql来维护.也会消耗内存和cpu资源. 索引实现原理 通过算法来实现: 常见有hash算法和b_tree树来实现. 把列中的值通过hash计算出一个整数值.把整数值单独存储成为索引列.用于记录位置.避免全表扫描…

NVME盘未格式化导致Ubuntu20.04启动慢

背景 最近公司一款产品转产&#xff0c;工厂组装好后&#xff0c;用我提供的系统镜像烧录&#xff0c;系统起来后发现Ubuntu20.04转圈了90秒才进入图形界面&#xff0c;这是不可接受的&#xff0c;公司老总要求当天必须解决。 定位 分析syslog 看不到系统启动时的日志&…