数据结构 快速排序的三种实现 (hoare版本 挖坑法 前后指针版本)与非递归实现

快速排序:快速排序算法通过多次比较和交换来实现排序
基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
适用情况:数据量大,要求的效率也高,应用广
时间复杂度和空间复杂度和稳定性:O(nlogn)~O(n2) ,O(logn)~O(n) 不稳定 (这里提到空间复杂度是因为其他排序都是O(1))

快排有三种实现方法,且都比较重要。
将区间按照基准值划分为左右两半部分的常见方式有:
1. hoare版本
2. 挖坑法
3. 前后指针版本

hoare版本

1、定义两个指针begin(指向首元素)和end(指向尾元素),找一个基准值key(一般三数取中法),置于序列的第一个元素,也就是begin的位置。
2、end从后往前走找比基准值小的元素(找到后也停下),begin从前往后走找比基准值key大的元素(找到后停下),
然后,交换arr[begin]和arr[end],依次循环操作。
3、当begin与end相遇,将array[begin]或array[end]与基准值交换。

详解:(建议和代码一起看)
在这里插入图片描述

代码

//获取基准值:三数取中法:起始, 中间, 结束
int GetMid(int* arr, int begin, int end)
{
	int mid = begin + (end - begin) / 2;
	if (arr[begin] > arr[mid])
	{
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return end;
		else
			return begin;
	}
	else
	{
		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}

//返回基准值位置
int Partion(int* arr, int begin, int end)
{
	//获取基准值的位置
	int mid = GetMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, mid);
	int key = arr[begin];
	int start = begin;

	while (begin < end)
	{
		//从后向前先找小于基准值的位置
		while (begin < end && arr[end] >= key)
		{
			--end;
		}
		//从前向后再找大于基准值的位置
		while (begin < end && arr[begin] <= key)
		{
			++begin;
		}
		Swap(arr, begin, end);
	}
	//交换相遇位置的数据和基准值
	Swap(arr, start, begin);
	return begin;
}

//数据有序时,没有优化可能会导致栈溢出(代码优化,重新找基准值)
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分之后,基准值的位置
	int div = Partion(arr, begin, end);
	//左右两部分进行快速排序
	QuickSort(arr, begin, div - 1);
	QuickSort(arr, div + 1, end);
}

运行结果
在这里插入图片描述

挖坑法

挖坑法和hoare版本很像
1、定义begin和end分别指向数据的第一个元素和最后一个元素,找一个基准值key(一般三数取中法),置array[begin]的位置上的值为基准值,并为第一个坑。
2、end从后往前走,找比key小的值,找到之后,将array[end]赋值给array[begin],填充begin位置的坑,此时end位置为一个新的坑
3、begin从前往后走,找比key大的值,找到之后,将array[begin]赋值给array[end],填充end位置的坑,此时begin位置为一个坑
4、此类方法依次填坑,当begin和end相遇,则循环结束,将key的值填最后一个坑。

详解:(建议和代码一起看)

在这里插入图片描述
代码

//获取基准值:三数取中法:起始, 中间, 结束
int GetMid(int* arr, int begin, int end)
{
	int mid = begin + (end - begin) / 2;
	if (arr[begin] > arr[mid])
	{
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return end;
		else
			return begin;
	}
	else
	{
		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}
//挖坑法
int Partion2(int* arr, int begin, int end)
{
	//获取基准值的位置
	int mid = GetMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, mid);
	int key = arr[begin];
	while (begin < end)
	{
		//从后向前先找小于基准值的位置
		while (begin < end && arr[end] >= key)
		{
			--end;
		}
		//填坑
		arr[begin] = arr[end];
		//从前向后再找大于基准值的位置
		while (begin < end && arr[begin] <= key)
		{
			++begin;
		}
		//填坑
		arr[end] = arr[begin];
	}
	//相遇位置存放基准值
	arr[begin] = key;
	return begin;
}
void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分之后,基准值的位置
	int div = Partion2(arr, begin, end);
	//左右两部分进行快速排序
	QuickSort(arr, begin, div - 1);
	QuickSort(arr, div + 1, end);
}

运行结果:
在这里插入图片描述

前后指针版本

1、选择一个基准值key,定义两个指针prev和cur(prev指向pPcur的前一个位置)
2、当cur标记的元素比key小时,prev和cur同时走,当cur标记的元素比key大时,只有cur继续向前走(此时prev停下来),当cur走到标记的元素比key值小时,cur停下,prev向前走一步,此时交换arr[cur]和arr[prev],然后,cur继续往前走。
3、当cur走出界了,将prev位置的值与key交换。

动画:
在这里插入图片描述
动画看不理解可以看图

详解:(建议和代码一起看)
在这里插入图片描述

代码:

//获取基准值:三数取中法:起始, 中间, 结束
int GetMid(int* arr, int begin, int end)
{
	int mid = begin + (end - begin) / 2;
	if (arr[begin] > arr[mid])
	{
		if (arr[mid] > arr[end])
			return mid;
		else if (arr[begin] > arr[end])
			return end;
		else
			return begin;
	}
	else
	{
		if (arr[mid] < arr[end])
			return mid;
		else if (arr[begin] < arr[end])
			return end;
		else
			return begin;
	}
}
//前后指针法
int Partion3(int* arr, int begin, int end)
{
	//获取基准值的位置
	int mid = GetMid(arr, begin, end);
	//把基准值放到起始位置
	Swap(arr, begin, mid);
	int key = arr[begin];
	int prev = begin;
	int cur = begin + 1;
	
	while (cur <= end)
	{
		if (arr[cur] < key && ++prev != cur)
		{
			//不连续 交换数据
			Swap(arr, prev, cur);
		}

		++cur;
	}
	Swap(arr, begin, prev);
	return prev;
}

void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	//div:一次划分之后,基准值的位置
	int div = Partion3(arr, begin, end);
	//左右两部分进行快速排序
	QuickSort(arr, begin, div - 1);
	QuickSort(arr, div + 1, end);
}

运行结果:

在这里插入图片描述

非递归实现快速排序

在数据结构中,即使递归能解决的,我们也有必要掌握非递归的方法。我们知道,堆空间比栈的空间大得多,当我们在栈中递归调函数,会增加栈的开销,栈帧会多。导致栈溢出程序崩溃。而在堆上递归调用函数,即使大的递归调用也不会使程序崩溃。这在安全方面就解决了栈溢出的问题。

递归就是自上而下,再自下而上。也就满足了我们数据结构中的栈。所以这里我们可以借助栈来实现非递归的快速排序。

这里直接放代码,配合代码+注释+图,能更好理解。

//非递归快排 ,避免栈溢出风险
void QuickSortNor(int* arr, int begin, int end)
{
	//定义栈
	Stack st;
	//初始化栈
	StackInit(&st);
	
	//先将末尾的索引入栈,
	StackPush(&st, end);
	//再将开头索引入栈
	StackPush(&st, 0);
	
	//只要栈不为空,就继续执行
	while (!StackEmpty(&st))
	{
		//获得栈顶元素
		int left = StackTop(&st);
		//出栈
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		
		//获取分开后区间的中间值
		int div = Partion(arr, left, right);

		//上一轮key值位置的左边[left, div - 1]
		if (left < div - 1)
		{
			StackPush(&st, div - 1);
			StackPush(&st, left);
		}
		//上一轮key值位置的右边[div + 1, right]
		if (div + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, div + 1);
		}
	}
}

在这里插入图片描述

运行结果:
在这里插入图片描述
我们可以看出,无论是递归还是非递归,我们都需要借助O(nlogn)的大小空间。(递归,栈的开销)(非递归,数据结构栈需要开空间)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值