快速排序的非递归实现、归并排序的递归和非递归实现、基数排序、排序算法的时间复杂度

news2024/11/22 18:08:09

文章目录

  • 快速排序的非递归
    • 三数取中法选取key
    • 快速排序三路划分
  • 归并排序的递归
  • 归并排序的非递归
  • 计数排序
  • 稳定性
  • 排序算法的时间复杂度

快速排序的非递归

我们使用一个栈来模拟函数的递归过程,这里就是在利用栈分区间。把一个区间分为 [left,keyi-1][key][keyi+1,right]递归下去,直至区间不存在或left > right。
如图所示:
在这里插入图片描述
先把整体的区间压进去,然后出栈,处理完毕后找到keyi再分为左右两个区间。然后往栈里压有区间,压左区间,就像树的后续遍历一样先把叶子区间处理,再处理分支节点的区间。
代码如下所示:

void QuickSortNonS(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, left);
	STPush(&st, right);
	while (!STEmpty(&st))
	{
		int right = STTop(&st);
		STPop(&st);
		int left = STTop(&st);
		STPop(&st);
		int keyi = PastSort1(a, left, right);
		//先入右区间
		if (keyi + 1 < right)
		{
			STPush(&st, keyi + 1);
			STPush(&st, right);
		}
		//再入左区间
		if (keyi - 1 > left)
		{
			STPush(&st, left);
			STPush(&st, keyi - 1);
		}
	}
	STDestory(&st);
}

三数取中法选取key

还可以选择三数取中法来选取key的值,原理是选取不大不小的数使得快速排序的交换次数变少。
代码如下:

//三数取中法选取Key
int GetMidKey(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] <= a[right]&&a[left] <= a[mid] && a[mid] <= a[right])
	{
		return mid;
	}
	else if(a[left]<=a[mid]&&a[left]<=a[right]&&a[left]<=a[mid])
	{
		return right;
	}
	else if (a[right]<=a[mid]&&a[right]<=a[left]&&a[left]<=a[mid])
	{
		return left;
	}
	else if (a[mid] <= a[left] && a[mid] <= a[right] && a[right] <= a[left])
	{
		return right;
	}
	else if (a[mid] <= a[right] && a[mid] <= a[left] && a[left] <= a[right])
	{
		return left;
	}
	else if (a[right] <= a[left] && a[right] <= a[mid] && a[mid] <= a[left])
	{
		return mid;
	}
	else
	{
		return left;
	}
}

快速排序三路划分

原理:规定左指针,cur指针,右指针。当a[cur] < key时,把a[cur]和a[left]的值交换cur++,left++。当a[cur] > key时,把a[cur]和a[right]的值交换,right–。当a[cur] > key时,cur++,具体的操作过程如下图所示:
在这里插入图片描述
代码如下:

//三路划分
//1、最小的在最左边
//2、最大的在最右边
//3、相等的在中间

void QuickSort1(int* a, int begin, int end)
{
	if (begin > end)
	{
		return;
	}
	//三路划分
	int keyi = GetMidKey(a, begin, end);
	Swap(&a[begin], &a[keyi]);
	int key = a[begin];
	int left = begin;
	int right = end;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			Swap(&a[cur], &a[left]);
			left++;
			cur++;
		}
		else if (a[cur] > key)
		{
			Swap(&a[cur], &a[right]);
			right--;
		}
		else
		{
			cur++;
		}
	}
	//[begin,left-1][left,right][right+1,end]
	QuickSort(a, begin, left-1);
	QuickSort(a, right + 1, end);
}

归并排序的递归

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
在这里插入图片描述
递归代码如下所示:

void _MergeSort(int* a, int begin, int end, int* temp)
{
	//递归结束条件
	if (begin >= end)
	{
		return;
	}
	//我们需要分区间进行,把区间可以分为
	//[begin,mid][mid+1,end]
	int mid = (begin + end) / 2;
	//通过后序遍历使得最小的区间有序[0,0][1,1][2,2]……[end-1,end-1][end,end]
	_MergeSort(a, begin, mid, temp);
	_MergeSort(a, mid+1, end, temp);
	//开始处理归并排序,也就是处理二叉树根的排序问题 左右根
	//这里相当于合并两个有序数组
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1<=end1&&begin2<=end2)
	{
		if (a[begin1] < a[begin2])
		{
			temp[i++] = a[begin1++];
		}
		else
		{
			temp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		temp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		temp[i++] = a[begin2++];
	}
	//再把数据拷回去
	memcpy(a+begin, temp+begin,sizeof(int)*(end-begin+1));
}

//归并排序递归算法
void MergeSort(int* a, int n)
{
	//我们需要开辟一个数组
	int* temp = (int*)malloc(sizeof(int) * n);
	//进行递归需要一个子函数
	_MergeSort(a, 0, n - 1, temp);
	free(temp);
}

递归展开图:
在这里插入图片描述

归并排序的非递归

原理:如下图所示:
在这里插入图片描述
划分为一个数一个数一组归并排序后再划分为两个数一组,然后再归并直至数组有序
代码如下:

//归并排序非递归
void MergeSortNonS(int* a, int n)
{
	int gap = 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	while (gap < n)
	{
		int j = 0;
		for (int i = 0; i < n; i += gap * 2)
		{
			//第一趟归并排序1数据归并成两个数,
			//第二趟2个数归为4个数
			//第三趟4个数,4和4归并成8个数
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			//一块一块进行拷贝
			/*if (end1 >= n || begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}*/
			//直接修正
			if (end1 >= n)
			{
				end1 = n - 1;
				//不存在的区间
				begin2 = n;
				end2 = n-1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			else if (begin2 >= n )
			{
				begin2 = n;
				end2 = n - 1;
			}
			printf("[%d %d][%d %d]\n", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					temp[j++] = a[begin1++];
				}
				else
				{
					temp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				temp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				temp[j++] = a[begin2++];
			}
			//一块一块进行拷贝
			//memcpy(a+i, temp+i, sizeof(int)*(end2-i)+1);
		}
		//直接拷贝
		memcpy(a, temp, sizeof(int) * n);
		gap *= 2;
	}
	free(temp);
}

这里需要注意溢出的三种情况
在这里插入图片描述
消除溢出的方法有两种,均已在代码注释中标出。

计数排序

计数排序应用了相对映射的方法
如下图所示:
在这里插入图片描述
代码实现:

//计数排序
void CountSort(int* a, int n)
{
	//首先找出最大值和最小值
	int max = a[0];
	int min = a[0];
	//相对映射
	for (int i = 0; i < n; i++)
	{
		if (max < a[i])
		{
			max = a[i];
		}
		if (min > a[i])
		{
			min = a[i];
		}
	}
	//求出范围
	int range = max - min;
	//开辟一个range大小的数组
	int* temp = (int*)malloc(sizeof(int) * range+1);
	for (int i = 0; i < n;i++)
	{
		temp[i] = 0;
	}
	//统计次数
	for (int i = 0; i < n; i++)
	{
		temp[a[i] - min]++;
	}
	//排序
	for (int i = 0; i < n; i++)
	{
		while (temp[i]--)
		{
			a[i] = i + min;
		}
	}
	free(temp);
}

稳定性

稳定的排序算法:冒泡排序,计数排序,归并排序,直接插入排序。
不稳定的排序算法 :堆排序,快速排序,选择排序,希尔排序。

排序算法的时间复杂度

基数排序
时间复杂度O(N+范围)
空间复杂度O(范围)

在这里插入图片描述

数据结构的初阶到此为止,高阶数据结构将用C++来描述。

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

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

相关文章

机器学习(13)--支持向量机

目录 一、支持向量机概述 二、Sklearn中的SVM概述 三、线性SVM损失函数 四、sklearn中进行可视化 1、导入模块 2、实例化数据集&#xff0c;可视化 3、网格点制作 4、建立模型并绘制决策边界和超平面 5、常用接口 6、如果数据是环形呢&#xff1f; 五、核函数 1、…

emacs下相对行号的设置

全局设置 全局开启行号显示&#xff1a;global-display-line-numbers-mode t 并设置 display-line-numbers-type的样式: relative 相对 配置代码如下: (use-package emacs:ensure t:config (setq display-line-numbers-type relative) (global-display-line-numbers-mode t)…

校园跑腿小程序如何运营赚钱,方法其实很简单

校园跑腿小程序是一种以满足大学生的校园内外需求为主要目标的创业方式。下面将详细介绍校园跑腿小程序的运营方式&#xff0c;包括市场调研、产品设计、用户获取与留存、服务流程管理等方面。 市场调研&#xff1a; 在开始校园跑腿小程序的运营之前&#xff0c;进行充分的市…

2023年经典【自动化面试题】附答案

一、请描述一下自动化测试流程&#xff1f; 自动化测试流程一般可以分为以下七步&#xff1a; 编写自动化测试计划&#xff1b; 设计自动化测试用例&#xff1b; 编写自动化测试框架和脚本&#xff1b; 调试并维护脚本&#xff1b; 无人值守测试&#xff1b; 后期脚本维…

【C++从0到王者】第九站:String基本介绍及使用

文章目录 一、String的基本使用二、String构造相关的成员函数1.String构造函数2.String析构函数3.operator运算符重载4.String增删查改之增5. operator[]运算符重载 三、String迭代器1.迭代器介绍2.string中迭代器的作用3.迭代器跟容器结合4.反向迭代器5.const修饰的迭代器 四、…

华夏ERP在虚拟机Ubuntu上的安装(测试实例)

1.虚拟机软件VirtualBOX 7.0 2.Ubuntu 版本 3.宝塔面板安装 百度搜索宝塔面板&#xff0c;按官网提示进行安装。下面截图是官网示例。 if [ -f /usr/bin/curl ];then curl -sSO download.cnnbt.net/install_panel.sh;else wget -O install_panel.sh download.cnnbt.net/install…

python接口自动化(三十七)-封装与调用--读取excel 数据(详解)

简介 在进行软件接口测试或设计自动化测试框架时&#xff0c;一个不比可避免的过程就是: 参数化&#xff0c;在利用python进行自动化测试开发时&#xff0c;通常会使用excel来做数据管理&#xff0c;利用xlrd、xlwt开源包来读写excel。例如&#xff1a;当我们登录的账号有多个的…

报错400是什么怎么解决呢?

首先要了解400错误是什么错误&#xff1a; HTTP状态码400表示"错误请求"。它是一种客户端错误状态码&#xff0c;表示服务器无法理解请求的语法或参数。当服务器收到一个无效的请求时&#xff0c;通常会返回400错误码。这可能是由于请求中缺少必要的参数、参数格式错…

亚马逊买家号怎么留评

要在亚马逊上留下产品评价&#xff0c;需要买家号有留评权限才行。以下是留评论的步骤&#xff1a; 1、登录亚马逊账号&#xff1a;使用您的买家账号和密码登录到亚马逊的网站或移动应用程序。 2、找到购买的产品&#xff1a;在亚马逊的网站或应用程序中&#xff0c;找到您购…

el-ment ui 表格组件table实现列的动态插入功能

在实际需求中我们经常遇到各种奇葩的需求&#xff0c;不足为奇。每个项目的需求各不相同&#xff0c;实现功能的思路大致是一样的。 本文来具体介绍怎么实现table表格动态插入几列。 首先实现思路有2种&#xff0c; 1. 插入的位置如果是已知的&#xff0c;我知道在哪个标题的…

WEB学习笔记3

输入输出语句 外部js&#xff1a;这种写法有一个不好的地方就是&#xff0c;两个不同的开发人员在使用不同的js写function的时候有可能造成重复&#xff0c;导致程序紊乱。在这推荐一种用json格式方式书写js函数的方法 变量&#xff1a; 变量命名规范&#xff1a; let和var的区…

DolphinScheduler使用问题记录

1.资源中心 功能板块 出现 storage not startup #问题原因 提示&#xff1a;“storage not startup”&#xff0c;顾名思义&#xff1a;未启用存储 #解决方式 1. 修改两个 common.properties 文件&#xff1a; api-server/conf/common.properties worker-server/conf/common.p…

分布式运用——存储系统Ceph

分布式运用——存储系统Ceph 一、Ceph 介绍1.Ceph 简介2、存储基础2.1 单机存储设备2.2 单机存储的问题2.3 商业存储解决方案2.4 分布式存储&#xff08;软件定义的存储 SDS&#xff09;2.5 分布式存储的类型 3.Ceph 优势3.1 高扩展性3.2 高可靠性3.3 高性能3.4 功能强大 4.Cep…

自动化测试——selenium(完结篇)

自动化测试——selenium&#xff08;完结篇) 文章目录 自动化测试——selenium&#xff08;完结篇)一、元素操作方法二、浏览器操作方法三、获取元素信息操作四、鼠标操作 &#xff08;需要实例化鼠标对象&#xff09;4.1 鼠标右键及双击4.2 鼠标拖拽4.3 鼠标悬停 【重点】 五、…

【JavaEE面试题(九)线程安全问题的原因和解决方案】

多线程-初阶 4. 多线程带来的的风险-线程安全 (重点)4.1 观察线程不安全原因是 1.load 2. add 3. save 4.2 线程安全的概念4.3 线程不安全的原因最根本的是 操作系统对线程的调度是随机的★1. 修改共享数据&#xff08;多个线程修改同一个变量&#xff09;★2. 操作不是原子性★…

vue使用window.addEventListener 监视网络状态中,箭头函数与function的区别

在vue中使用window.addEventListener监视网络状态时&#xff0c;遇到一个坑&#xff0c;只能说自己跟不是步伐&#xff0c;知识困乏&#xff0c;不知道箭头函数和function函数的区别。 最初vue监视网络状态的方法是这样的&#xff1a; window.addEventListener("online&q…

【C语言】字符串函数

文章目录 一、求字符串长度strlen例子模拟实现 二、长度不受限制的字符串函数strcpy例子模拟实现 strcat例子模拟实现 strcmp例子模拟实现 三、长度受限制的字符串函数strncpy例子 strncat例子 strncmp例子 四、字符串查找strstr例子模拟实现 strtok例子 五、错误信息报告strer…

Linux 这20个 systemd 命令值得运维工程师收藏

systemd是一种Linux系统初始化和管理守护进程的系统和服务管理器。它引入了一组命令行工具&#xff0c;用于管理和监控系统状态、服务单元和日志。 1. systemdctl systemdctl命令用于管理systemd系统和服务单元。以下是一些常用的systemdctl命令&#xff1a; 启动一个服务单…

API 自动化测试指南

目录 前言&#xff1a; 什么是 API 测试&#xff1f; 为什么 API 测试很重要&#xff1f; 测试金字塔 GUI 测试 单元测试 API 测试 API 负载测试 API 测试工具如何选择 如何测试 Web 服务 HTTP 关于 HTTP 请求 请求行&#xff08;HTTP 方法&#xff09; 标头 请…

Jenkins持续集成项目搭建 —— 基于Python Selenium自动化测试

第一步&#xff1a;去官网Jenkins下载最新的war包 第二步&#xff1a;安装.war包即&#xff1a;安装jinkens 打开命令窗口&#xff0c;进入.war包所在的路径下 执行java -jar jenkins.war命令 安装成功的标志如图2所示 在浏览器中试一下是否成功输入&#xff1a;0.0.0.0:8080进…