【数据结构】图解八大排序(下)

news2024/11/25 0:47:57

文章目录

  • 一、前言
  • 二、快速排序
    • 1. hoare 版
    • 2. 挖坑法
    • 3. 前后指针法
    • 4. 快排的非递归实现
    • 5. 时空复杂度分析
  • 三、归并排序
    • 1. 递归实现
    • 2. 非递归实现
  • 四、计数排序

一、前言

在上一篇文章中,我们已经学习了五种排序算法,还没看过的小伙伴可以去看一下:传送门
今天要讲的是八大排序中剩下的三种,这三种排序算法用的是非常多的,需要好好掌握。
下面介绍的排序算法均以排升序为例。

二、快速排序

快排的思想是分治,就是选定一个基准值,使这个值的左边都小于它,右边都大于它,然后递归处理左右子区间。

因此快排的关键就在于如何选定基准值和如何实现划分左右区间。
关于前者,我们可以选左端点,也可以选右端点,也可以选中点,还可以随机选。我们不妨先以左端点为基准值,来继续下面的分析。
关于后者,其实有很多种方法可以实现划分,这里只讲几种比较经典的。

1. hoare 版

hoare是快排的发明者,我们先来学习他的划分方法。
给几分钟认真观察下面的动图。
在这里插入图片描述
以排升序为例,我们要使左边都小于key,右边都大于key,就可以利用左右双指针来维护这段区间,右指针找小,左指针找大,找到后交换,再继续找,直至左右指针相遇为止。最后把key与相遇处的值交换即可。

先来解释下为什么选左端点为基准值的时候,要右指针先走。
先说结论,这样做是为了保证最后左右指针相遇处的值比key小,从而保证key与相遇位置的值交换后,左边都小于key,右边都大于key。同理如果以右端点为基准值,就要左指针先走。
再来解释下为什么会这样。左右指针相遇无非两种情况,要么左遇右,要么右遇左。
如果左遇右,因为右指针先走,所以右指针先停。由于右指针是找小,所以右指针停的位置的值比key小,所以相遇处的值就比key小。
如果右遇左,也就是说右指针还没停,因为右指针是先走的,一种情况是左指针根本没动过,还停留在左端点;另一种情况是左指针动过,但是上一轮左右指针指向的值交换后,左指针位置的值就比key小了。所以上述两种情况都能保证key与左右指针相遇处的值交换后,左边都小于key,右边都大于key

来看看代码怎么写。

void QuickSort1(int a[], int l, int r)
{
	if (l >= r) return;

	int keyi = l;//左端为key,右端先走
	int i = l, j = r;//保存一下左右指针的初始位置
	while (l < r)
	{
		while (l < r && a[r] >= a[keyi]) r--;//右边找小
		while (l < r && a[l] <= a[keyi]) l++;//左边找大
		swap(&a[l], &a[r]);
	}
	swap(&a[keyi], &a[l]);
	keyi = l;

	QuickSort1(a, i, keyi - 1);
	QuickSort1(a, keyi + 1, j);
}

第一趟结束后,因为key的左边都比它小,右边都比它大,所以key已经在最终排好序的位置上了。故只需递归[l, keyi - 1][keyi + 1, r]即可。

2. 挖坑法

这种方法比较好理解,直接上动图和代码。
在这里插入图片描述

void QuickSort2(int a[], int l, int r)
{
	if (l >= r) return;

	int key = a[l];
	int hole = l;
	int i = l, j = r;
	while (l < r)
	{
		while (l < r && a[r] >= key) r--;
		a[hole] = a[r];
		hole = r;
		while (l < r && a[l] <= key) l++;
		a[hole] = a[l];
		hole = l;
	}
	a[hole] = key;

	QuickSort2(a, i, hole - 1);
	QuickSort2(a, hole + 1, j);
}

3. 前后指针法

在这里插入图片描述

void QuickSort3(int a[], int l, int r)
{
	if (l >= r) return;

	int keyi = l;
	int prev = l, cur = l + 1;
	while (cur <= r)
	{
		if (a[cur] < a[keyi])
			swap(&a[++prev], &a[cur]);

		cur++;
	}
	swap(&a[prev], &a[keyi]);
	
	QuickSort3(a, l, prev - 1);
	QuickSort3(a, prev + 1, r);
}

以上三种方法效率并无差异,只是实现划分的思想不同,挑一种自己好理解的掌握就行。个人比较喜欢 hoare 版的快排。

4. 快排的非递归实现

众所周知,函数栈帧是建立在栈空间上的,而栈空间的大小是有限的,如果递归过深可能会导致栈溢出。因此算法的非递归实现具有很重要的意义。

我们可以利用数据结构中的栈来模拟递归的过程。

void QuickSortNonR(int a[], int l, int r)
{
	ST st;
	STInit(&st);
	STPush(&st, r);
	STPush(&st, l);

	while (!STEmpty(&st))
	{
		l = STTop(&st);
		STPop(&st);
		r = STTop(&st);
		STPop(&st);

		//这里是hoare版的划分思想
		int keyi = l;
		int i = l, j = r;
		while (i < j)
		{
			while (i < j && a[j] >= a[keyi]) j--;
			while (i < j && a[i] <= a[keyi]) i++;
			swap(&a[i], &a[j]);
		}
		swap(&a[keyi], &a[i]);
		keyi = i;
	
		//如果区间长度==2
		//它的两个子区间长度就分别为1和0,故不用继续递归
		if (r - keyi > 2)//r - (keyi + 1) + 1
		{
			STPush(&st, r);
			STPush(&st, keyi + 1);
		}
		if (keyi - l > 2)//(keyi - 1) - l + 1
		{
			STPush(&st, keyi - 1);
			STPush(&st, l);
		}
	}

	STDestroy(&st);
}

由于 C 语言的特性,我们只能自己实现栈这个数据结构,具体可以看我这篇文章:【数据结构】栈和队列

另外,快排的非递归还可以用队列实现,有兴趣的可以自己尝试。
提示:栈模拟的是dfs,队列模拟的是bfs

5. 时空复杂度分析

快排的平均时间复杂度是O(NlogN),数组有序的情况下会退化成O(N^2)
空间复杂度是O(logN)

快排是不稳定的排序算法。

三、归并排序

归并排序的思想是分治,就是先递归到最后一层,此时每个子区间都只有一个数,即每个子区间都有序,每两个子区间再合并成一个有序区间,最后扫尾。

在这里插入图片描述

完成这个过程需要一个辅助数组,另外要开辟logN层栈帧,故空间复杂度为O(N)
在这里插入图片描述

归并排序的时间复杂度是O(NlogN)
归并排序是稳定的排序算法。

来看看代码怎么写。

1. 递归实现

void _MergeSort(int a[], int l, int r, int* tmp)
{
	if (l >= r) return;

	int mid = l + r >> 1;
	_MergeSort(a, l, mid, tmp);
	_MergeSort(a, mid + 1, r, tmp);

	int i = l, j = mid + 1, k = l;
	while (i <= mid && j <= r)
	{
		if (a[i] <= a[j]) tmp[k++] = a[i++];
		else tmp[k++] = a[j++];
	}

	while (i <= mid) tmp[k++] = a[i++];
	while (j <= r) tmp[k++] = a[j++];

	for (int i = l; i <= r; i++) a[i] = tmp[i];
}

void MergeSort(int a[], int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

2. 非递归实现

void MergeSortNonR(int a[], int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	int gap = 1;
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += 2 * gap)
		{
			// 每组的合并数据
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			// 修正
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			// 归并一组,拷贝一组
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}

	free(tmp);
}

归并排序的缺点在于O(N)的空间复杂度和拷贝数组的消耗。
因此归并排序的思想更多的是用于外部排序。

四、计数排序

计数排序的思想是相对映射。

直接来看代码怎么实现。

void CountSort(int a[], int n)
{
	int min = 0, max = 0;
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min) min = a[i];
		if (a[i] > max) max = a[i];
	}
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));

	for (int i = 0; i < n; i++)
	{
		int idx = a[i] - min;
		count[idx]++;
	}

	for (int i = 0, j = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}

	free(count);
}

时间复杂度 O(N + range)
空间复杂度 O(range)

缺陷:只适用于数据范围较为集中的数组,且只能用于整型,因为计数排序是用计数数组的下标来相对映射原数组的值的。

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

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

相关文章

C语言 —— 浮点类型详解及 IEEE754 规定

【C语言趣味教程】(3) 浮点类型&#xff1a;单精度浮点数 | 双精度浮点型 | IEEE754 标准 &#x1f517; 《C语言趣味教程》&#x1f448; 猛戳订阅&#xff01;&#xff01;&#xff01; ​—— 热门专栏《维生素C语言》的重制版 —— &#x1f4ad; 写在前面&#xff1a;这是…

Paragon NTFS2023中文最新版mac读写NTFS移动硬盘

当我们使用macOS系统将数据拷贝或写入NTFS格式磁盘时&#xff0c;却发现不能操作成功。搜索原因或解决方案时&#xff0c;许多网友推荐安装磁盘管理软件——Paragon NTFS for Mac。 往往大家都会有两个疑问&#xff0c;一是为什么非要使用NTFS格式的磁盘&#xff1f;二是为什么…

C/C++程序内存区域划分以及各区域的介绍

C/C程序内存区域划分 直接上图&#xff1a; 在这里插入图片描述 注&#xff1a;以下的说明均已VS2019为例 栈区&#xff08;stack&#xff09; 在执行函数时&#xff0c;函数内局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结束时这些存储单元会自动释放。栈内…

用自己的数据拟合Sigmoid函数(Matlab平台)

%% 拟合sigmoid曲线 sigmoid (params, x) params(1) ./ (1 exp(-params(2) .* (x - params(3)))) params(4); %params(1) 是斜率参数&#xff0c;params(2) 是增长速率参数&#xff0c;params(3) 是 x 值的偏移参数&#xff0c;params(4) 是 y 值的偏移参数。 initialGuess…

剑指offer刷题笔记--Num51-60

1--数组中的逆序对&#xff08;51&#xff09; 主要思路&#xff1a; 基于归并排序&#xff0c;视频讲解参考&#xff1a;数组中的逆序对 #include <iostream> #include <vector>class Solution { public:int reversePairs(std::vector<int>& nums) {if(…

【力扣算法17】之 19. 删除链表的倒数第 N 个结点 python

文章目录 问题描述示例1示例2示例3提示 思路分析代码分析完整代码详细分析运行效果截图完结 问题描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例1 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例2…

加强安全防护,提升日志管理——探索EventLog Analyzer

导言&#xff1a; 在当今数字化时代&#xff0c;安全威胁和数据泄露已经成为各个组织和企业面临的严峻挑战。有效的日志管理和监控是确保网络安全的重要一环。本文将介绍EventLog Analyzer&#xff08;事件日志分析器&#xff09;这一强大工具&#xff0c;探索其在日志安全方面…

五、DQL-2.基本查询

一、数据准备 1、删除表employee&#xff1a; drop table employee; 2、创建表emp&#xff1a; 3、添加数据&#xff1a; 4、查看表数据&#xff1a; 【代码】 -- 查询数据--------------------------------------------------------- drop table emp;-- 数据准备-----------…

uni-app image加载错误 404 替换为默认图片

双层v-for 使用item修改 aitem.cat_icon || defaultPic绑定图片src属性为aitem.cat_icon 如果aitem.cat_icon的值为空字符串或undefined&#xff0c;那么默认图片defaultPic被显示出来当图片加载错误时,触发handleImageError方法,将aitem传进去 <!-- 页面--><view …

windows下mysql8定时备份,bat脚本编写,dos免密执行

前提&#xff1a;mysql8已经安装。 编写脚本copy_mysql_data.bat echo off set timestamp%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2% set backupfileD:\ProgramData\MySQL\Backup\backup_%timestamp%.sql set mysqlpathD:\Program Files\MySQL\MyS…

成为一个年薪30W+的DFT工程师是一种什么体验?

一直以来&#xff0c;DFT都是数字IC设计行业中相对神秘的一个岗位。 你说他重要吧&#xff0c;并不是所有芯片设计公司都有这个岗位&#xff0c;你说他不重要吧&#xff0c;但凡芯片产品达到一定规模后&#xff0c;就必须设置DFT部门。 一、什么是DFT&#xff1f; DFT&#x…

1haclon 简单操作

文章目录 *读取图片 read_image(Image,claudia) *转换为灰度 rgb1_to_gray(Image,GrayImage)阈值分割 区域连接 获取最衣服 *读取图片 read_image(Image,claudia) *转换为灰度 select_shape (Connection, SelectedRegions, area, and, 40963.3, 44724.8) rgb1_to_gray(Image,Gr…

AI绘画 | 迷人武士美少女战士作品集

今天用Midjourney生成了质量极高的美少女武士后续会作为固定栏目来分享美图接下来请欣赏作品 提示词分享&#xff1a;1.an asian girl dressed in samurai style, in the style of anime aesthetic, trick of the eye paintings, dollcore, light red and black, resin, 8k, ex…

教程 | 如何10秒内一键生成高质量PPT

Hi! 大家好&#xff0c;我是赤辰&#xff01; 近期新进的学员不少职场小白&#xff0c;对AI工具提效办公很感兴趣&#xff0c;今天火速给大家安排&#xff0c;ChatGPTMindShow强强联合&#xff0c;30秒内快速生成PPT&#xff0c;对于策划小白来说简直是福音呀&#xff01; 市…

Java 多文件压缩与解压

1&#xff0c;写多个文件到压缩包 import org.apache.tools.zip.ZipEntry;import org.apache.tools.zip.ZipFile;import org.apache.tools.zip.ZipOutputStream;public static void main(String[] args) {//压缩文件对象String zipPath "D:\\download\\files0719.zip&quo…

15 大模型训练 内存优化

先看GPU结构&#xff0c;我们常说显存的时候&#xff0c;说的一般就是Global memory 训练的过程中&#xff0c;我们为了反向传播过程&#xff0c;必须将中间的结果&#xff08;激活值&#xff09;存储下来。 在训练的过程中&#xff0c;那些会消耗内存呢&#xff1f; model we…

ubuntu20.04系统安装使用labelme标注数据集

一、Anaconda的安装 请参考&#xff1a;MediapipeVSCodeAnaconda 实时检测手部关键点并保存视频_苦瓜汤补钙的博客-CSDN博客 二、Labelme的安装 1.打开终端创建虚拟环境 # 创建labelme的环境 conda create -n labelme python3.9 输入“y”&#xff0c;然后回车。 2.激活虚拟…

为什么媒体和创意工作者更喜欢 Splashtop 进行远程访问

在当今快速发展的数字环境中&#xff0c;可靠的远程访问已成为全球媒体和创意工作者不可或缺的工具。 远程访问让创意工作者不再受传统办公空间边界的限制&#xff0c;完全实现了随时随地办公。无论是实时剪辑、高分辨率渲染还是其他创意任务&#xff0c;创意工作者对高性能远…

Go语言之包管理

1、package Go语言是使用包来组织源代码的&#xff0c;包&#xff08;package&#xff09;是多个 Go 源码的集合&#xff0c;是一种高级的代码复用方案。Go语言中为我们提供了很多内置包&#xff0c;如 fmt、os、io 等。任何源代码文件必须属于某个包&#xff0c;同时源码文件的…

新书发布!《数字化时代研发效能跃升方法与实践》作者序丨IDCF

作者团队&#xff1a; 姚冬、王立杰、徐磊、许舟平 为什么会有本书 只是因为在人群之中&#xff0c;多看了你一眼。这一切&#xff0c;都要从IDCF的发端讲起 缘起IDCF 时间如白马过隙&#xff0c;我们成立IDCF一晃已有四年&#xff0c;而距离上一本书《敏捷无敌之DevOps时…