快速排序和归并排序非递归的详解

news2025/4/3 1:00:50

在经过主页中《八大排序》(下)的学习,我们了解了快速排序和归并排序且都是递归的思想,但是如果递归的深度很深呢?这一节我们就引出用非递归的思想解决这个问题。😵😵😵

快速排序非递归及归并排序非递归

  • 快速排序非递归
    • 思想
    • 代码
    • 快排非递归总结
  • 归并排序非递归
    • 思想
    • 代码
    • 归并非递归总结
  • 总代码
    • Stack.h
    • Stack.c
    • Sort.c
  • 结语

快速排序非递归

大家都知道递归是在栈帧上建立空间(Windows默认1兆,Linux默认8兆),如果递归的深度太深,建立的栈帧过大呢?
在这里插入图片描述
**递归的缺陷:**如果栈帧的深度太深,栈空间不够用,就会导致栈溢出。

思想

既然递归有一定的缺陷,那么我们怎么确定非递归的思路呢?

  • 1.直接改循环(但是只能针对比较简单的);
  • 2.借助数据结构的栈来模拟递归过程(针对复杂情况)。

针对快排我们就用第二种思路。第二种非递归思想中用到的栈是我们malloc出来的,是在堆上申请的,堆上的空间很大,和操作系统的栈并 不是一样的。😋😋😋

步骤:

  • 单趟和挖坑法一样,既然是模拟递归,那么和递归是很相似的
  • 1.先在序列中选出一个基准值keyIndex,并将这个keyIndex放到相应地位置上,使左边比keyIndex小,右边比keyIndex大;
  • 2.将整个区间划分三个区间:[left,keyIndex-1] keyIndex [keyIndex+1,right],由于栈是先进后出的原则,先把右区间压栈到下面,左区间压栈到上面;
  • 3.如果左半区间内有多个数据,重复步骤1和步骤2,直到左半区间全部处理完了,再去处理右区间;
  • 4.如果右半区间内有多个数据,重复步骤1和步骤2,当右半区间有序后,整个序列就就是有序的。
    在这里插入图片描述

代码

void QuickSortNonR(int* a,int n)
{
	ST st;
	StackInit(&st);//初始化一个栈
	StackPush(&st, n - 1);//把数组中最后一个元素入进去
	StackPush(&st, 0);//把数组中第一个元素入进去
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);//取栈顶数据
		StackPop(&st);//出栈

		int right = StackTop(&st);
		StackPop(&st);

		//单趟排选谁无所谓
		int keyIndex = PartSort1(a, left, right);//这里单趟排序用的是挖坑法,前后指针和左右指针都可以。
		//[left,keyIndex-1] keyIndex [keyIndex+1,right]此时数组被分为了三个区间
		//入栈先入右区间,再入左区间,这样就可以先处理左区间,再处理右区间
		if (keyIndex + 1 < right)//表示右区间内还有多个数据,无序就入栈
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}
		if (left < keyIndex - 1)//表示左区间内还有多个数据,无序就入栈
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}

	StackDestory(&st);
}
void TestQuickSort()
{
	int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
	QuickSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

快排非递归总结

快排非递归思想性能和递归思想两者性能几乎没有差距,但是非递归很好的解决了栈溢出问题。

归并排序非递归

思想

在上文中我们了解到递归改非递归有两种,一种是循环解决,另一种是用栈模拟递归😊😊😊
这里我们使用循环就完全可以了。归并排序递归思想和非递归思想恰恰相反,递归思想是将序列分解为不可再分的子序列再进行归并,而非递归是将序列直接以不可再分的子序列进行归并。如图:
在这里插入图片描述

代码

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	int gap = 1;//每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//[i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//拷贝回去--把临时数组内的元素拷贝到原数组中
			
		}
		for (int j = 0; j < n; ++j)
		{
			a[j] = tmp[j];
		}
		gap *= 2;//刚才是11一组归并,*=2让2倍归并
	}

	free(tmp);
}
void TestMergeSort()
{
	int a[] = { 10, 6, 7, 1, 3, 9, 4, 2 };
	MergeSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
	printf("归并排序-非递归:");
	TestMergeSort();
	return 0;
}

问题:😧😧😧

上面是我们理想的状态,就像满二叉树一样,数组长度不是2的整数次方的话,就会存在数组越界的情况,总结一下有以下三种情况:
在这里插入图片描述

  • 1.归并过程中左区间存在,但是右半区间不存在;
  • 2.归并过程中左区间存在,但是右半区间有且只有一部分;
  • 3.归并过程中左半区间有且只有一部分,右半区间不存在。

问题处理:😖😖😖

对上面三个问题的解决:

  • 1.那就不需要归并了
    if (begin2 >= n) break;
  • 2.将end2修正一下
    if (end2 >= n) { end2 = n - 1; }
  • 3.不对左半区间进行处理,将拷贝回去的代码放到for循环里,归并一部分拷贝一部分
    for (int j = i; j <= end2; ++j) { a[j] = tmp[j]; }
//归并排序——非递归
//时间复杂度是O(N*logN)
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);

	int gap = 1;//每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2*gap)
		{
			//[i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//归并过程中右半区间可能不存在
			if (begin2 >= n)
				break;
			//归并过程中右半区间有且不多(算多了)修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//拷贝回去--把临时数组内的元素拷贝到原数组中
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;//刚才是11一组归并,*=2让2倍归并
	}

	free(tmp);
}
void TestMergeSort()
{
	int a[] = { 10, 6, 7, 1, 3, 9, 4, 2 };
	MergeSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

归并非递归总结

时间复杂度还是不错的和归并排序递归是一样的都是O(N*logN)。

总代码

Stack.h

#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//是一个指针指向这个数组
	int top;//栈顶
	int capacity;//容量
}ST;

//栈需要的接口
// 初始化栈
void StackInit(ST* ps);
// 销毁栈
void StackDestory(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//取栈顶的数据
STDataType StackTop(ST* ps);
//栈的数据个数
int StackSize(ST* ps);
//检测栈是否为空,如果为空返回非零结果,如果非空返回0 
bool StackEmpty(ST* ps);//用布尔来判断真假更好用,但是注意引用头文件

Stack.c

#include"Stack.h"

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;
}

void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	//满了怎么办?---增容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;//乘等2才会变成它的二倍
		}
	}

	ps->a[ps->top] = x;
	ps->top++;
}

void StackPop(ST* ps)
{
	assert(ps);
	//栈空了,调用pop,直接中止程序并报错
	assert(ps->top > 0);

	ps->top--;
}

STDataType StackTop(ST* ps)
{
	assert(ps);
	//栈空了,调用top,直接中止程序并报错
	assert(ps->top > 0);

	return ps->a[ps->top - 1];
}

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;//top所在数组的下标就是栈的长度
}

bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;//如果为空返回非零结果,如果不为空返回0
}

Sort.c

这里因为快速排序非递归中用到了单趟排序,所以直接在拷贝了一份挖坑法的单趟排序,不理解的可以去看《八大排序》(下)

#include"Stack.h"
void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
//交换
void Swap(int* p1, int* p2)
{
	int* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中--为了解决我们key选择的数不是最大,也不是最小的
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] < a[mid])//如果左小于中间
	{
		if (a[mid] > a[right])//如果左小于中间,中间大于右,则返回中间
		{
			return mid;
		}
		else if (a[left] > a[right])//如果左小于中间,左大于右,则返回左
		{
			return left;
		}
		else
		{
			return right;//如果左小于中间,中间大于右,则返回右
		}
	}
	else //a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
int PartSort1(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);//三数取中
	//Swap(&a[left], &a[index]);
	int begin = left, end = right;
	int pivot = begin;
	int key = a[begin];
	//单趟排序
	//单趟排序时间复杂度是O(N)-begin从左向右走,end从右向左走
	while (begin < end)
	{
		//右边找小,放到左边
		while (begin < end && a[end] >= key)
			--end;
		//小的放到左边的坑里,自己形成新的坑位
		a[pivot] = a[end];
		pivot = end;

		//右边找大
		while (begin < end && a[begin] <= key)
			++begin;
		//大的放到右边的坑里,自己形成新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	//把小的放到pivot的左边,大的放到pivot的右边后,把key的值放到数组pivot的位置
	pivot = begin;
	a[pivot] = key;

	//返回的是坑的位置
	return pivot;
}




//快速排序--非递归
void QuickSortNonR(int* a, int n)
{
	ST st;
	StackInit(&st);//初始化一个栈
	StackPush(&st, n - 1);//把数组中最后一个元素入进去
	StackPush(&st, 0);//把数组中第一个元素入进去
	while (!StackEmpty(&st))//当栈里没有区间的时候就结束
	{
		int left = StackTop(&st);
		StackPop(&st);

		int right = StackTop(&st);
		StackPop(&st);

		//单趟排选谁无所谓
		int keyIndex = PartSort1(a, left, right);//这里单趟排序用的是挖坑法,前后指针和左右指针都可以。
		//[left,keyIndex-1] keyIndex [keyIndex+1,right]此时数组被分为了三个区间
		//入栈先入右区间,再入左区间,这样就可以先处理左区间,再处理右区间
		if (keyIndex + 1 < right)//表示右区间内还有多个数据,无序就入栈
		{
			StackPush(&st, right);
			StackPush(&st, keyIndex + 1);
		}
		if (left < keyIndex - 1)//表示左区间内还有多个数据,无序就入栈
		{
			StackPush(&st, keyIndex - 1);
			StackPush(&st, left);
		}
	}

	StackDestory(&st);//栈销毁
	//非递归中malloc的空间是在操作系统的堆上面的,因为堆很大,所以空间并没有什么影响,
	//malloc的空间是在操作系统上的,与数据结构的栈(栈和队列)和堆(二叉树)没有关系
}
void TestQuickSort()
{
	int a[] = { 49, 38, 65, 97, 76, 13, 27, 49 };
	QuickSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

//归并排序——非递归
//时间复杂度是O(N*logN)
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	int gap = 1;//每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//[i,i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//归并过程中右半区间可能不存在
			if (begin2 >= n)
				break;
			//归并过程中右半区间有且不多(算多了)修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
			//拷贝回去--把临时数组内的元素拷贝到原数组中
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;//刚才是11一组归并,*=2让2倍归并
	}

	free(tmp);
}
void TestMergeSort()
{
	int a[] = { 10, 6, 7, 1, 3, 9, 4, 2 };
	MergeSortNonR(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

int main()
{
	printf("快速排序-非递归:");
	TestQuickSort();

	printf("归并排序-非递归:");
	TestMergeSort();
	return 0;
}

结语

熬夜写的,写着写着脑子就不太灵光了,懵懵的😩😩😩文章中有错误欢迎大家积极指出哦😚😚😚

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

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

相关文章

根据给定数组,创建形状相同的数组并且采用不同方式填充full_like()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 根据给定数组&#xff0c;创建形状相同的数组 并且采用不同方式填充 full_like() [太阳]选择题 对下面代码中full_like函数结果描述错误的选项为&#xff1f; import numpy as np print(&q…

谷粒学院——Day05【后台系统前端项目创建、讲师管理模块前端开发】

后台系统前端项目创建 一、vue-element-admin 简介 vue-element-admin 是基于 element-ui 的一套后台管理系统集成方案。 功能&#xff1a;https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能 GitHub地址&#xff1a;https://github.com/PanJiaChen/vue-ele…

分布式锁_Redis分布式锁+Redisson分布式锁+Zookeeper分布式锁+Mysql分布式锁(原版)

分布式锁_Redis分布式锁Redisson分布式锁Zookeeper分布式锁Mysql分布式锁&#xff08;原版&#xff09; 文章目录分布式锁_Redis分布式锁Redisson分布式锁Zookeeper分布式锁Mysql分布式锁&#xff08;原版&#xff09;1. 传统锁回顾1.1. 从减库存聊起1.2. 环境准备1.3. 简单实现…

Dreamweaver网页设计与制作100例——HTML5期末考核大作业——票务网站整套网页

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

正确查询DO基站IP

对于DO站的IP地址在系统中设置是否正确需要确定基站侧IP地址和RNC侧地址是否匹配&#xff0c;匹配关系为&#xff1a;基站侧IP地址减2即为RNC侧地址&#xff08;如&#xff1a;RCS 234 BTS-IP: 6.33.84.30 则匹配RNC侧地址即为6.33.84.28&#xff09;&#xff0c;下面举例进行襄…

基于单片机的语音小车设计

目 录 引言 1 1 系统概述 1 1.1 声控产品前景和发展趋势 1 1.2 研究目的和意义 1 1.3 本次设计内容 2 2 系统设计的整体方案 2 2.1 主控芯片的方案论证 2 2.2 语音识别模块的方案论证 3 2.3 电机驱动方案选择 4 2.4 本章小节 4 3 系统…

使用ssh克隆GitHub仓库以及替换https方式

目录 使用ssh克隆GitHub仓库 第一步&#xff1a;生成ssh 第二步&#xff1a;添加SSH key 第三步&#xff1a;验证绑定是否成功 第四步&#xff1a;克隆 意外的情况&#xff1a; 情况1&#xff1a;ssh连接GitHub失败 情况2&#xff1a;使用git clone 不成功 替换原来的…

队列的简单实现

队列的简单实现一、什么是队列二、队列的分类三、队列的数据结构四、队列的基本操作1、初始化队列2、销毁队列3、入队4、出队5、队列判空6、获取队头元素7、获取队尾元素8、获取队列元素总结头文件基本操作一、什么是队列 首先我们既然想要实现队列就得明白什么是队列&#xff…

1.7.4、计算机网络体系结构中的术语

1.7.4、计算机网络体系结构中的术语 1.7.4.1、实体 实体&#xff1a; 任何可发送或接收信息的硬件或软件进程。 对等实体&#xff1a; 收发双方相同层次中的实体 1.7.4.2、协议 协议&#xff1a;控制两个的对等实体进行逻辑通信的规则的集合 之所以称为逻辑通信&#xf…

目标检测论文解读复现之五:改进YOLOv5的SAR图像舰船目标检测

目标检测论文解读复现 文章目录目标检测论文解读复现前言一、摘要二、网络模型及核心创新点三、应用数据集四、实验效果&#xff08;部分展示&#xff09;五、实验结论六、投稿期刊介绍前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改…

HTML5期末考核大作业,电影网站——橙色国外电影 web期末作业设计网页

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

【代码精读】ATF的异常向量表

快速链接: . 👉👉👉 【代码精读】–Kernel/ATF/optee等-目录👈👈👈 付费专栏-付费课程 【购买须知】:本专栏的视频介绍-----视频👈👈👈概要: 本文概述了ARMv8/ARMv9的aarch64体系中异常向量表的结构、以及基地寄存器的总结。然后通过导读ATF BL31的异常向量…

Flink系列文档-(YY09)-Flink时间语义

1 三种时间语义 在实时流式计算中&#xff0c;"时间"是一个能影响计算结果的非常重要因素&#xff01; 试想场景&#xff1a;每隔1分钟计算一次最近10分钟的活跃用户量&#xff1a; ①假设此刻的时间是13:10&#xff0c;要计算的活跃用户量时间段为&#xff1a;[ …

【C++】类和对象(下)

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;再谈构造…

kindle自定义屏保之自定义字帖

kindle自定义屏保之自定义字帖 01 前言 毕业以后&#xff0c;很少动笔写字了&#xff0c;某天要手写一堆材料&#xff0c;写出来实在不忍直视&#xff0c;于是当晚下班后突发奇想——能不能把一些字帖搞成kindle屏保&#xff0c;摆在桌面上&#xff0c;睡前说不准还能练练 随…

web课程设计 基于html+css+javascript+jquery女性化妆品商城

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

软件测试基础知识

软件测试基础知识1.测试模型2.测试分类3.测试目的与原则4.测试流程5.测试发展规划6.单元测试7.黑盒测试8.白盒测试9.缺陷1.测试模型 瀑布模型 开发将系统都做好了&#xff0c;然后测试。最大问题是测试工作后置&#xff0c;导致整个项目开发完成之后如果发现比较重要的问题&…

基于微信小程序的校运会管理系统设计与实现-计算机毕业设计源码+LW文档

小程序开发说明 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Mave…

【大学课程设计】计算器实现(附源码)

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

6.Paddle Graph Learning (PGL)图学习之图游走类模型[系列四]

Paddle Graph Learning (PGL)图学习之图游走类模型[系列四] 更多详情参考&#xff1a;Paddle Graph Learning 图学习之图游走类模型[系列四] https://aistudio.baidu.com/aistudio/projectdetail/5002782?contributionType1 相关项目参考&#xff1a; 关于图计算&图学习…