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

文章目录

  • 一、前言
  • 二、快速排序
    • 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)

缺陷:
1.只适合用于数据范围较为集中的情况。
如果range过大,时间复杂度和空间复杂度都会很大,时空效率都很一般。
但是在range较小的情况下,时间复杂度可以做到线性,空间复杂度可以做到常数级。
2.只能用于整型数据的排序。
因为计数排序是用计数数组的下标来相对映射原数组每个元素的值,而数组下标只能是整型。

总之,计数排序在排序范围较集中的整型数据时,效率非常好,大幅优于其他排序算法。

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

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

相关文章

python将dataframe数据导入MongoDB非关系型数据库

文章目录 pymongo连接新建数据库和集合pandas导入数据插入数据数据查看 pymongo连接 import pymongo client pymongo.MongoClient("mongodb://localhost:27017/") dblist client.list_database_names() for db in dblist:print(db) #查看已有数据库admin bilibil…

接口测试 react+unittest+flask 接口自动化测试平台

目录 1 前言 2 框架 2-1 框架简介 2-2 框架介绍 2-3 框架结构 3 平台 3-1 平台组件图 1 新建用例 2 生成测试任务 3 执行并查看测试报告 3-2 用例管理 3-2-1 用例设计 3-3 任务管理 3-3-1 创建任务 3-3-2 执行任务 3-3-3 测试报告 3-3-4 邮件通知 1 前言 构建…

科技资讯|Apple Vision Pro新专利,关于相对惯性测量系统的校正

美国专利商标局正式授予苹果一项 Apple Vision Pro 相关专利&#xff0c;该专利涵盖了具有视觉校正功能的相对惯性测量系统。这样的系统用于弥补头显内的眼前庭不匹配&#xff0c;当 VR 头显中发生的事情与现实世界环境中发生的运动不匹配时&#xff0c;可能会导致恶心。 苹果…

怎么用Midjourney制作表情包

要使用Midjourney制作表情包&#xff0c;可以按照以下步骤进行操作&#xff1a; 1. 打开Midjourney的官方网站或下载Midjourney应用程序&#xff0c;并登录你的账户。 2. 在Midjourney中&#xff0c;选择创建新项目或表情包。 3. 在项目中&#xff0c;你可以选择使用预设的模…

笔记本电脑的电池健康:确保长时间使用和优异性能的关键

笔记本电脑已经成为我们日常生活中不可或缺的工具&#xff0c;无论是办公、学习还是娱乐&#xff0c;我们都依赖着它的便携性和高效性能。而在所有的硬件组件中&#xff0c;电池健康被认为是确保长时间使用和良好性能的关键因素之一。一块健康的电池不仅能提供持久的续航时间&a…

list模拟实现

一、结点的定义 有三个成员&#xff0c;2个指向前面和后面的指针&#xff0c;一个表示结点存储T类型的值。 对于_prev和_next&#xff0c;类型是 list_node<T>*&#xff0c;不是list_node*&#xff0c;加上类型参数T之后&#xff0c;才是模板类的类型。 构造函数中&am…

大模型时代,腾讯云“复制”腾讯|WAIC2023

点击关注 文&#xff5c;郝鑫 编&#xff5c;刘雨琦 刚过去的WAIC&#xff08;世界人工智能大会&#xff09;俨然成为了大模型厂商的成果汇报大会。 百度文心大模型升级到3.5版本&#xff0c;训练速度提升2倍&#xff0c;推理速度提升30倍&#xff1b;华为云发布盘古大模型3…

【数学建模】统计分析方法

文章目录 1.回归分析2. 逻辑回归3. 聚类分析4. 判别分析5. 主成分分析6. 因子分析7. 对应分析 1.回归分析 数据量要多&#xff0c;样本总量n越大越好——>保证拟合效果更好&#xff0c;预测效果越好 一般n>40/45较好 方法 建立回归模型 yiβ0β1i……βkxkiεi 所估计的…

❤️创意网页:HTML5,canvas创作科技感粒子特效(科技感粒子、js鼠标跟随、粒子连线)

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

Gateway网关组件(在Spring Cloud整合Gateway(idea19版本))

Spring Cloud Gateway官网:Spring Cloud Gateway 局域网中就有网关这个概念&#xff0c;局域网接收数据或发送数据都要通过网关&#xff0c;比如使用VMware虚拟机软件搭建虚拟机集群的时候&#xff0c;往往我们需要选择IP段中的⼀个IP作为网关地址,网关可以对请求进行控制,提升…

CSS科技感四角边框

实现效果:使用before和after就可以实现,代码量不多,长度颜色都可以自己调整 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><style>*{margin:0;padding:0;}html,body{…

MySQL数据备份与恢复

目录 ​编辑 一、数据备份 1.1物理备份 1.1.1冷备份 1.1.2热备份 1.1.3温备份 二、逻辑分区 2.1完全分区 2.2差异分区 2.3增量备份 三、数据备份恢复实验 3.1做一个数据 3.2物理冷备份与恢复 3.3mysqldump 备份与恢复&#xff08;温备份&#xff09; 3.4Mysql数据…

TinyKv流程梳理三

split流程 处理协程启动 func (bs *Raftstore) startWorkers(peers []*peer) {ctx : bs.ctxworkers : bs.workersrouter : bs.routerbs.wg.Add(2) // raftWorker, storeWorkerrw : newRaftWorker(ctx, router)go rw.run(bs.closeCh, bs.wg)sw : newStoreWorker(ctx, bs.store…

GIT常用命令汇总

文章目录 新建仓库git clone克隆一个仓库指定版本到指定文件夹克隆带子模块的仓库 子模块添加子模块删除子模块切换子模块分支更新子模块 GIT IGNORE忽略文件添加忽略某个文件夹下所有文件刷新.gitignore使其生效 参考链接&#xff1a; 新建仓库 git clone 语法如下&#xff…

Centos 7 安装 Oracle 11G

Oracle 11G 安装教程 准备环境 p13390677_112040_Linux-x86-64_1of7.zipp13390677_112040_Linux-x86-64_2of7.zipCentos 7- rhel7-英文版的系统–不想换语言的执行(LANGen_US)– 传输 文件到服务器上 创建用户和组 [rootlocalhost ~]# groupadd oracle [rootlocalhost ~]…

ASUS华硕飞行堡垒8笔记本FX506LH LI LU FX706原装出厂 Win10系统工厂模式20H2

自带所有驱动、出厂主题壁纸LOGO、Office办公软件、MyASUS电脑管家、奥创控制中心等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;HDI,SWP,OFS,EDN,KIT,TLK多个底包 文件大小&#xff1a;10.95GB 注&#xff1a;恢复时会清空电脑上所有盘的数据&…

OpenCV中的RGB与YUV转换

1 基本概念 YUV 颜色空间从模拟电视时代开始就被广泛应用于彩色图像的转换与处理。其基于一个 3x3 的矩阵&#xff0c;通过线性变换将 RGB 像素转换为一个亮度&#xff08;Luma&#xff09;分量 Y 以及两个色度&#xff08;Chroma&#xff09;分量 U 和 V。由于模拟电视存在着多…

曲师大2023大一新生排位赛-B.Sort题解

题目描述 插入排序是一种非常常见且简单的排序算法。王同学是一名大一的新生&#xff0c;今天许师哥刚刚在上课的时候讲了插入排序算法。 假设比较两个元素的时间为 &#xff0c;则插入排序可以以 的时间复杂度完成长度为 n&#xfffd; 的数组的排序。不妨假设这 n 个数字分…

JavaWeb(4)——HTML、CSS、JS 快速入门

一、JavaScript 数组 数组筛选&#xff08;查找&#xff0c;将原来数组中的某些数据去除&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…

【Hydro】一个简单的HBV水文模型产流Python实现

说明 HBV模型包括一系列自由参数&#xff0c;其值可以通过率定得到。同时也包括一些描述流域和气候特征的参数&#xff0c;它们的值在模型率定是假定不变。子流域的划分使得在一个子流域中可能有很多参数值。虽然在大多数应用中&#xff0c;各子流域之间参数值只有很小的变化&a…