排序算法之快速排序

排序算法之快速排序



前言

在C语言阶段,我们学习过如何使用C语言库中提供的快速排序
在进行更深层次的学习以后,本篇博客主要讲述如何实现快速排序
包括快排递归版本与非递归版本


提示:以下是本篇文章正文内容,下面案例可供参考

一、快排的基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法
其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

什么?没看懂? 我们可以详看hoare版本的单趟操作动图

二、递归实现快排

注意:基准值key,可以取所有待排元素的任意一个,根据本人习惯此篇文章基准值都是最左边元素
也就是 keyi = left 基准值的位置不同代码可能略有不同 但大体的思路是相同的,同时基准值key与快排的效率息息相关,我们尽量使key的值接近与该区间的中间大小,使得效率更高,本篇博客利用三数取中法获取基准值,并且会手动将基准值放在最左边

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2;

	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] >= a[mid]
	{
		if (a[right] > a[left])
		{
			return left;
		}
		else if (a[mid] > a[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}

}

1、hoare版本

hoare版本一趟排序的基本思想如下:
在这里插入图片描述

我们在所有元素内随机找一个元素作为key 在走了一趟排序之后
我们可以得到key左边元素都比key小或者与key相等,key右边元素都比key大或者与key相等
同时将key放到left与right相遇的地方

实现代码

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
	int keyi = GetMidIndex(a, left, right);
	Swap(&a[keyi], &a[left]);

	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;
}

2、挖坑法

在这里插入图片描述

这个方法原理与hoare原理相似,但更好理解,左边找大右边找小,在不断挖坑的同时也在填坑

实现代码

// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
	int keyi = GetMidIndex(a, left, right);
	Swap(&a[keyi], &a[left]);

	keyi = left;

	int key = a[left];
	int hole = left;

	while (left < right)
	{

		//右边找小
		while (left < right && a[right] >= key)
		{
			right--;
		}
		//Swap(&a[hole], &a[right]);
		a[hole] = a[right];
		hole = right;


		//左边找大
		while (left < right && a[left] <= key)
		{
			left++;
		}

		a[hole] = a[left];
		hole = left;
	}

	a[hole] = key;

	return hole;
}

3、前后指针法

在这里插入图片描述

前后指针法就比较巧妙,总结来说只在做以下几件事
cur找小,a[cur] < key,++prev,交换prev与cur位置的值
1、最开始prev与cur相邻
2、当cur遇到比key大的值之后,cur++,prev不动,此时他们之间的值都是比key大的值
3、cur找小,找到小的之后,跟++prev位置的值交换,这个过程相等于把比key大的翻滚式往右边推同时把小的换到左边

实现代码

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
	int keyi = GetMidIndex(a, left, right);
	Swap(&a[keyi], &a[left]);

	keyi = left;

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

	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}

		cur++;
	}

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

	return keyi;
}

以上是单趟排序的三种方法,这三种方法都实现的结果都相同 key左边元素都比key小或者与key相等,key右边元素都比key大或者与key相等
这时可以将得到的区间为 [ 0 , key-1 ] key [ key+1 , n-1 ] ,继续将左区间和右区间 分别进行单趟排序操作,每个区间完成之后可以继续分为三段区间,继续重复以上操作
直到区间内只有一个元素,那么这个元素必定有序
当这个区间左边有序,右边也有序时,整体就有序了

这个过程可以近似看作二叉树的前序遍历,所以整个快排需要用递归来实现
而递归的结束条件就是这个区间只有一个元素,或者没有元素

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

	int keyi = PartSort3(a, left, right);

	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

看到这里有的小伙伴可能会问一个问题,left与right相遇时,会将a[left] 与 a[keyi]交换也就是说a[left]是比key小的,那么这里如何保证a[left]是比基准值key小的呢

结论:

1、左边做key,右边先走;保障了相遇位置的值比key小
2、右边做key,左边先走;保障了相遇位置的值比key大

我们以左边做key为例进行解释:
在这里插入图片描述

三、非递归实现快排

关于快排的非递归实现,其实本质上是利用栈的性质,也就是先进后出的性质来模拟递归的过程,这个过程很像利用栈实现二叉树的前序遍历

在实现的时候,left与right作为一组数据入栈和出栈

代码如下

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		left = STTop(&st);
		STPop(&st);

		right = STTop(&st);
		STPop(&st);

		int keyi = PartSort1(a, left, right);

		if (right > keyi + 1)
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);
}


四、快速排序的特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值