排序(堆排序、快速排序、归并排序)-->深度剖析(二)

news2024/12/23 5:08:17

前言

前面介绍了冒泡排序、选择排序、插入排序、希尔排序,作为排序中经常用到了算法,还有堆排序快速排序归并排序

堆排序(HeaSort)

堆排序的概念

堆排序是一种有效的排序算法,它利用了完全二叉树的特性。在C语言中,堆排序通常通过构建一个最大堆(或最小堆),然后逐步调整堆结构,最终实现排序。

代码实现

堆排序是一种基于二叉堆的排序算法,它通过将待排序的元素构建成一个二叉堆,然后逐步移除堆顶元素并将其放置在数组的尾部,同时维护堆的性质,直至所有元素都被排序。

注意:第一个for循环中的(n-1-1)/ 2 的含义

  • 第一个减1是由n-1个元素
  • 第二个减1是除以2是父亲节点。以为我们调整的是每一个根节点。(非叶子节点)
//堆排序
void HeapSort(int* a, int n)
{
	//建堆
	for(int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a,n,i);
	}


	//排序
	int end = n - 1;
	while(end > 0)
	{
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		--end;
	}	
}

其中AdjustDown是建立堆的函数,我们要建立一个大堆,将替换到上上面的小值,向下调整,保持大堆的结构。

代码如下:

//向下调整
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}

	}

}

堆排序的复杂度分析

堆排序是一种常用的排序算法,其时间复杂度通常为O(nlogn)。在C语言中实现堆排序时,时间复杂度的分析主要涉及到两个阶段:构建初始堆和进行堆排序。

  • 构建初始堆:从最后一个非叶子节点开始,逐步向上调整,直到根节点满足堆的性质。这个过程的时间复杂度为O(n),因为需要对每个非叶子节点进行一次调整。
  • 进行堆排序:堆排序的过程涉及到多次交换堆顶元素和最后一个元素,并对剩余的元素进行调整。每次交换后,堆的大小减一,并对新的堆顶元素进行调整。这个过程的时间复杂度为O(nlogn),因为每次调整的时间复杂度为O(logn),总共需要进行n-1次调整。

快速排序(Quick Sort)

快速排序的概念

快速排序(Quick Sort)是一种高效的排序算法,它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再分别对这两部分记录继续进行排序,以达到整个序列有序的目的。在C语言中,快速排序的实现通常涉及到递归函数的编写,以及对数组进行分区(partition)操作。

霍尔版本(hoare)

在这里插入图片描述

在这里我们是要,定义一个关键字(keyi)进行分区,然后分别向左右进行递归。

代码实现

int part1(int* a, int left, int right)
{
	int mid = GetmidNum(a,left,right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
			right--;
		while (left < right && a[left] <=a[keyi])
			left++;
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	keyi = left;
	return keyi;
}

挖坑法

挖坑法类似于霍尔方法,挖坑就是记住关键字,保证关键字就是一个坑位,比关键字值小(大)的时候就入坑位,此时,这个值位置作为新的坑位直至两个前后指针指向同一个坑位。

在这里插入图片描述

代码实现

int part2(int* a, int left, int right)
{
	int mid = GetmidNum(a, left, right);

	Swap(&a[left], &a[mid]);
	int keyi = a[left];
	
	int hole = left;
	while (left < right)
	{
		while (left < right && a[right] >= keyi)
			right--;
		Swap(&a[hole], &a[right]);
		hole = right;
		while (left < right && a[left] <= keyi)
			left++;
		Swap(&a[hole], &a[left]);
		hole = left;
	}
	Swap(&keyi, &a[hole]);
	keyi = left;
	return keyi; 

}

前后指针法

  • prev 指针初始化为数组的开始位置,cur 指针初始化为 prev 的下一位置。

  • cur 指针向前遍历数组,寻找小于或等于基准值的元素,而 prev 指针则跟随 cur 指针的移动,直到 cur 找到一个小于基准值的元素。

  • 一旦找到这样的元素,prevcur 指针之间的元素都会被交换,然后 cur 指针继续向前移动,直到找到下一个小于基准值的元素,或者到达数组的末尾。最后,基准值会被放置在 prev 指针当前的位置,完成一次排序

在这里插入图片描述

代码实现

int part3(int* a, int left, int right)
{
	int mid = GetmidNum(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	int cur = left + 1;
	int prev = left;
	while (cur <= right)
	{
		while (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);

		++cur;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

递归实现

以上都是递归方法,通过调用分区进行排序。

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;


	int key = part3(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);

}

快速排序迭代实现(利用栈)参考:栈和队列

基本步骤
  1. 初始化栈:创建一个空栈,用于存储待排序子数组的起始和结束索引。
  2. 压栈:将整个数组的起始和结束索引作为一对入栈。
  3. 循环处理,在栈非空时,重复以下步骤:
    • 弹出一对索引(即栈顶元素)来指定当前要处理的子数组。
    • 选择子数组的一个元素作为枢轴(pivot)进行分区。
    • 进行分区操作,这会将子数组划分为比枢轴小的左侧部分和比枢轴大的

代码实现

void QuickSortNonR(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STpush(&st, left);
	STpush(&st, right);

	while (!STEmpty(&st))
	{
		int end = STTop(&st);
		STPop(&st);
		int begin = STTop(&st);
		STPop(&st);

		int keyi = part3(a, begin, end);

		if (keyi + 1 < end)
		{
			STpush(&st, keyi + 1);
			STpush(&st, end);
		}

		if (begin < keyi - 1)
		{
			STpush(&st, begin);
			STpush(&st, keyi - 1);
		}
	}

	STDestroy(&st);
}

快速排序的优化

优化角度从两个方面切入

  1. 在选择关键字的(基准值)时候,如果我们碰到了,有序数组,那么就会,减低排序效率。
    • 方法一:三数取中,即区三个关键字先进行排序,将中间数作为关键字,一般取左端右端和中间值。
    • 方法二:随机值。利用随机数生成。

三数取中代码实现

int GetmidNum(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
		{
			return mid;
		}
		else if(a[end]<a[begin])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else //a[begin]>a[mid]
	{
		if (a[begin] < a[end])
		{
			return begin;
		}
		else if (a[end] < a[mid])
		{
			return mid;
		}
		else
		{
			return end;
		}
	}

随机选 key(下标) 代码实现

srand(time(0));
int randi = left + (rand() % (right - left));
Swap(&a[left], &a[randi]);

快速排序复杂度分析

  • 在平均情况下,快速排序的时间复杂度为O(n log n),这是因为每次划分都能够将数组分成大致相等的两部分,从而实现高效排序。在最坏情况下,快速排序的时间复杂度为O(n^2)
  • 除了递归调用的栈空间之外,不需要额外的存储空间,因此空间复杂度是O(log n)。在最坏情况下,快速排序的时间复杂度可能是 O(n),这是因为在最坏情况下,递归堆栈空间可能会增长到线性级别。

根据上述描述,优化快速排序是必要的。

归并排序(Merge Sort)

在这里插入图片描述

归并排序的概念

归并排序(Merge Sort)是一种基于分治策略的排序算法,它将待排序的序列分为两个或以上的子序列,对这些子序列分别进行排序,然后再将它们合并为一个有序的序列。归并排序的基本思想是将待排序的序列看作是一系列长度为1的有序序列,然后将相邻的有序序列段两两归并,形成长度为2的有序序列,以此类推,最终得到一个长度为n的有序序列。

基本操作:

  • 分解:将序列每次折半划分,递归实现,直到子序列的大小为1。
  • 合并:将划分后的序列段两两合并后排序。在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。这两个有序序列段分别为 R[low, mid]R[mid+1, high]。先将他们合并到一个局部的暂存数组 R2 中,合并完成后再将 R2 复制回 R 中。

代码实现(递归)

void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;

	_MergeSort(a, tmp, begin, mid - 1);
	_MergeSort(a, tmp, mid + 1, end);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;

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

		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	 
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));

}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

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

	free(tmp);
}

代码实现(迭代)

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		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;

			int j = i;

			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 log n),其中 n 是待排序元素的数量。这个时间复杂度表明,归并排序的执行速度随着输入大小的增加而线性增加,但不会超过对数级的增长。
  • 空间复杂度为 O(n),在数据拷贝的时候malloc一个等大的数组。

总结

p[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 log n),其中 n 是待排序元素的数量。这个时间复杂度表明,归并排序的执行速度随着输入大小的增加而线性增加,但不会超过对数级的增长。
* 空间复杂度为 O(n),在数据拷贝的时候malloc一个等大的数组。

# 总结

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8d8d45e2fc8b4b0fa4747b27d20cd50c.png)






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

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

相关文章

【Linux】:环境变量

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux环境变量的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门…

万字总结随机森林原理、核心参数以及调优思路

万字总结随机森林原理、核心参数以及调优思路 在机器学习的世界里&#xff0c;随机森林&#xff08;Random Forest, RF&#xff09;以其强大的预测能力和对数据集的鲁棒性而备受青睐。作为一种集成学习方法&#xff0c;随机森林通过构建多个决策树并将它们的预测结果进行汇总&…

SpringCloud_Eureka注册中心

概述 Eureka是SpringCloud的注册中心。 是一款基于REST的服务治理框架&#xff0c;用于实现微服务架构中的服务发现和负载均衡。 在Eureka体系中&#xff0c;有两种角色: 服务提供者和服务消费者。 服务提供者将自己注册到Eureka服务器&#xff0c;服务消费者从Eureka服务器中…

禹神electron学习~

最近时间比较富裕 咱们浅浅来学习下electron 视频在这禹神&#xff1a;一小时快速上手Electron&#xff0c;前端Electron开发教程_哔哩哔哩_bilibili 先看下流程模型 先决条件 首先第一步 查看你的node和npm版本 创建你的应用 创建一个文件夹 我创建的名称为my-electron-…

在Zotero中使用Deepl翻译

文章目录 Zotero简介Zotero下载插件下载在Zotero中安装插件获取Deepl密钥在Zotero中使用deepl 参考链接 Zotero简介 Zotero是一款非常实用的文献管理软件&#xff0c;可以快速帮助我们下载、分类和标注文献。由于专业需要&#xff0c;很多使用者需要阅读外文文献&#xff0c;Z…

无锁编程——从CPU缓存一致性讲到内存模型(1)

一.前言 1.什么是有锁编程&#xff0c;什么是无锁编程&#xff1f; 在编程中&#xff0c;特别是在并发编程的上下文中&#xff0c;“无锁”和“有锁”是描述线程同步和资源访问控制的两种不同策略。有锁&#xff08;Locked&#xff09;: 有锁编程是指使用锁&#xff08;例如互…

Redis-分布式锁(基本原理和不同实现方式对比)

文章目录 1、基本原理2、不同实现方式 1、基本原理 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&am…

Mysql 的账户管理,索引,存储引擎

目录 一.MySQL的账户管理 1.存放用户信息的表 2.查看当前使用的用户 3.新建用户 4.修改用户名称 5.删除用户 6.修改用户密码 7.破解密码 8. 远程登录 9.用户权限管理 9.1 权限类别 9.2 查看权限 9.3 授予权限 9.4 撤销权限 二.索引 1. 索引管理 1.1 查看索…

Generating Diverse Structure for Image Inpainting With Hierarchical VQ-VAE

Jialun Peng1 Dong Liu1* Songcen Xu2 Houqiang Li1 1 University of Science and Technology of China 2 Noahs Ark Lab, Huawei Technologies Co., Ltd.pjlmail.ustc.edu.cn, {dongeliu, lihq}ustc.edu.cn, xusongcenhuawei.com 原文提供代码链接&#xff1a; GitHub - UST…

MySQL:数据类型

数据类型 1. 字符串类型2. 整数类型3. 定点数类型和浮点数类型4. 布尔类型5. 枚举和集合类型6. 日期和时间类型7. Blob类型8. JSON类型 字符串类型、数字类型、日期和时间类型、存放二进制的数据类型、存放地理数据的类型。 1. 字符串类型 字符串类型也可以用来存储邮编&…

【论文阅读】-- 研究时间序列可视化,提升用户体验

Investigating Time Series Visualisations to Improve the User Experience 摘要1 引言2 相关工作互动技巧视觉编码坐标系 3 用户研究时间序列可视化互动技巧任务实验设计 4 结果交互技术的效果视觉编码的影响坐标系的影响 5 讨论交互技术的效果视觉编码的影响坐标系的影响 6 …

(必看图文)Hadoop集群安装及MapReduce应用(手把手详解版)

前言 随着大数据时代的到来&#xff0c;处理和分析海量数据已成为企业和科研机构不可或缺的能力。Hadoop&#xff0c;作为开源的分布式计算平台&#xff0c;因其强大的数据处理能力和良好的可扩展性&#xff0c;成为大数据处理领域的佼佼者。本图文教程旨在帮助读者理解Hadoop集…

《昇思25天学习打卡营第5天|数据变换 Transforms》

文章目录 前言&#xff1a;今日所学&#xff1a;1. Common Transforms2. Vision Transforms3. Text Transforms 前言&#xff1a; 我们知道在进行神经网络训练的时候&#xff0c;通常要将原始数据进行一系列的数据预处理操作才会进行训练&#xff0c;所以MindSpore提供了不同类…

C语言部分复习笔记

1. 指针和数组 数组指针 和 指针数组 int* p1[10]; // 指针数组int (*p2)[10]; // 数组指针 因为 [] 的优先级比 * 高&#xff0c;p先和 [] 结合说明p是一个数组&#xff0c;p先和*结合说明p是一个指针 括号保证p先和*结合&#xff0c;说明p是一个指针变量&#xff0c;然后指…

蒂升电梯职业性格和Verify认知能力SHL测评答题攻略及薪资待遇解密!

​一、蒂升电梯职业性格和认知能力测评考什么 您好&#xff01;蒂升电梯公司邀请您参加的OPQ职业性格测评和Verify认知能力测评是两种常见的评估工具&#xff0c;用于帮助了解个人的职场性格特点和认知能力。 OPQ职业性格测评 这是一种性格测试&#xff0c;通常用于评估个人在…

一文讲解Docker入门到精通

一、引入 1、什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;它允许在一台物理机上创建多个独立的虚拟环境&#xff0c;这些环境被称为虚拟机&#xff08;VM&#xff09;。每个虚拟机都可以…

盘古5.0,靠什么去解最难的题?

文&#xff5c;周效敬 编&#xff5c;王一粟 当大模型的竞争开始拼落地&#xff0c;商业化在B端和C端都展开了自由生长。 在B端&#xff0c;借助云计算向千行万业扎根&#xff1b;在C端&#xff0c;通过软件App和智能终端快速迭代。 在华为&#xff0c;这家曾经以通信行业起…

Java登录管理功能的自我理解(尚庭公寓)

登录管理 背景知识 1. 认证方案概述 有两种常见的认证方案&#xff0c;分别是基于Session的认证和基于Token的认证&#xff0c;下面逐一进行介绍 基于Session 基于Session的认证流程如下图所示 该方案的特点 登录用户信息保存在服务端内存&#xff08;Session对象&#xff…

Django 一对多关系

1&#xff0c;创建 Django 应用 Test/app9 django-admin startapp app9 2&#xff0c;注册应用 Test/Test/settings.py 3&#xff0c;添加应用路由 Test/Test/urls.py from django.contrib import admin from django.urls import path, includeurlpatterns [path(admin/,…

安装KB5039212更新卡在25% 或者 96% 进度

系统之家7月1日消息&#xff0c;微软在6月11日的补丁星期二活动中&#xff0c;为Windows 11系统推出了KB5039212更新。然而&#xff0c;部分用户在Windows社区中反映&#xff0c;安装过程中出现失败&#xff0c;进度条在25%或96%时卡住。对于遇到此类问题的Windows 11用户&…