【C数据结构】快速排序

一、快速排序

1、什么是快速排序

假设有一组数组
在这里插入图片描述
在其中任选一个基准值,假设是6
在这里插入图片描述
通过一些方法将序列分成左序列和右序列,左序列都小于基准值,右序列都大于基准值
只是将基准值的位置确定了,其它的数只是范围确定了,位置不一定对
在这里插入图片描述
之后分别对左序列和右序列用同样的方式,让每个数在相应位置上。


接下来的介绍的方法,就是为了实现基准值在相应位置上。

2、快排的三种方法(包括递归与非递归)

2.1、快排的挖坑法

假设对一个数组进行实现升序。 我们选定第一个数为基准值,在将基准值赋值给变量key后,将原来第一个数的位置看作一个坑,
并且在开头和最后位置建立变量begin、end,和建立变量pivot表示坑以便访问。

在这里插入图片描述

第一步:左移动end,若end位置的值大于基准值(6),则end-1访问上一个位置,若end位置的值小于基准值(6),则将end位置的值放入pivot位置,而end位置就变成了pivot的位置。
在这里插入图片描述
第二步:移动begin,若begin位置的值小于基准值(6),则begin+1访问下一个位置,若begin位置的值大于基准值(6),则将begin位置的值放入pivot位置,而begin位置又变成了pivot的位置。
在这里插入图片描述
重复第一步和第二步,直到begin与end位置相等,再将基准值放入,这样就确定好了6的相应位置
在这里插入图片描述

值得注意的是,在pivot下标中是有值的,因为这个值是可以被覆盖,所以我们将其抽象为坑。
在这里插入图片描述
接下来对左右序列再次进行以上操作:
在这里插入图片描述

用递归代码实现:

//快排挖坑法
void QuickSort(int* a, int left, int right)
{
	int begin = left, end = right;//begin,end需要改变的

	//递归的限制条件,序列只剩一个或者序列为空返回。
	if (begin >= end)
	{
		return;
	}

	int pivot = begin;
	int tmp = a[pivot];

	//实现一次让基准值在相应位置
	while (begin < end)
	{
		//注意begin < end,当begin和end相等时停止
		while (begin < end && a[end] >= tmp)
		{
			end--;
		}
		a[pivot] = a[end];
		pivot = end;
		while (begin < end && a[begin] <= tmp)
		{
			begin++;
		}
		a[pivot] = a[begin];
		pivot = begin;
	}
	
	//再次将基准值定义为序列的第一个数
	pivot = begin;
	a[pivot] = tmp;

	//访问左序列和右序列
	//[left,pivot-1] pivot [pivot+1,right]
	QuickSort(a, left, pivot - 1);
	QuickSort(a, pivot + 1, right);
}

在单趟排序中,通过右边访问和左边下标访问,它们的次数总和就是N(数的数量),而在递归当中,结构呈现了一个树形结构,那么他的次数就是高度次,也就是logN,所以时间复杂度为O(N*logN)。

在让我们来看看,快排中什么情况下是最坏的。
答案是有序的情况下,让我们来看看具体
假设是1 2 3 4 … n

在这里插入图片描述
从这可见,确实基准值如果取到第一位是有问题的。
那么如何解决这个问题呢?
对于解决的方法,能够在有序的情况下,使得拆分的时候结构呈现较均匀树形结构,每次找到基准值后能够拆出左右两段,而不是有序时的每次一边的一大段。

三数取中这个方法就很好的解决了这个问题。
三数取中:从数组最开始位置、末尾位置和中间位置的三个数,挑第二大的值,并且和数组第一个值进行交换。
这样效率又能回到N*logN
在这里插入图片描述

用递归代码实现:

//三数取中
int GetMid(int* a, int left, int right)
{
	int mid = (right + left) >> 1;
	if (a[left] < a[mid])
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[mid] < a[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

//快排挖坑法
void QuickSort(int* a, int left, int right)
{
	int begin = left, end = right;//begin,end需要改变的

	//递归的限制条件,序列只剩一个或者序列为空返回。
	if (begin >= end)
	{
		return;
	}
	
	int mid = GetMid(a, left, right);//三数取中
	Swap(&a[left], &a[mid]);
	int pivot = begin;
	int tmp = a[pivot];

	//实现一次让基准值在相应位置
	while (begin < end)
	{
		//注意begin < end,当begin和end相等时停止
		while (begin < end && a[end] >= tmp)
		{
			end--;
		}
		a[pivot] = a[end];
		pivot = end;
		while (begin < end && a[begin] <= tmp)
		{
			begin++;
		}
		a[pivot] = a[begin];
		pivot = begin;
	}
	
	//再次将基准值定义为序列的第一个数
	pivot = begin;
	a[pivot] = tmp;

	//访问左序列和右序列
	//[left,pivot-1] pivot [pivot+1,right]
	QuickSort(a, left, pivot - 1);
	QuickSort(a, pivot + 1, right);
}
2.2、快排的双指针法和hoare法

双指针法:

假设取一个基准值keyi(6),创建一个cur、一个prev指向如下位置。
在这里插入图片描述
每次对cur所在位置的值和keyi比较,如果比keyi小,先prev++,再交换prev和cur位置的值,交换后cur位置再++,直到cur越界停止,最后交换prev和keyi的值。
在这里插入图片描述
cur到达边界
在这里插入图片描述
之后再拆成左右两个区间,进行重复的方法。

//双指针快排
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int midi = GetMid(a, left, right);
	Swap(&a[left], &a[midi]);

	int keyi = left;
	int prev = left;
	int cur = prev + 1;

	while (cur <= right)
	{
		//先比较,如果小于了再进行++prev
		if (a[cur] < a[keyi] &&
			++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;

	}
	Swap(&a[keyi], &a[prev]);
	keyi=prev;
	
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

hoare法:
还是假设取一个基准值keyi(6),创建left、right分别在如下位置。
在这里插入图片描述
如果right位置的值大于等于keyi,那么right–,直到遇到小于keyi的值停止,再通过left找到值大于keyi的,直到left和right坐标都分别找到了大于的值和小于的值,交换两个位置的值。
在这里插入图片描述
直到left==right,交换left位置和keyi位置的值。
在这里插入图片描述
这时又固定了基准值的位置,然后分别对左右两个区间进行重复的方法。

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

	int midi = GetMid(a, left, right);
	Swap(&a[left], &a[midi]);

	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[left], &a[keyi]);
	
	keyi=left;
	
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
2.3、快排的非递归实现

递归的最大缺点就是,当递归深度过深的时候,栈帧创建的太多会导致栈溢出(Stack overflow)。
在这里插入图片描述

所以当出现递归深度过高的时候,我们不得不考虑非递归。

而将递归代码改成非递归代码时,我们有:

  1. 用循环取代递归
  2. 用数据结构中的栈模拟递归过程

在这里插入图片描述
在这里插入图片描述
代码实现:

//快排的非递归实现
void QuickSortR(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 index = partion1(a, left, right);

		//[left,index-1]index[index+1,right]
		if (index + 1 < right)
		{
			StackPush(&st, right);
			StackPush(&st, index + 1);
		}

		if (left < index - 1)
		{
			StackPush(&st, index - 1);
			StackPush(&st, left);
		}

	}


	StackDestroy(&st);
}

本章完

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值