快速排序【超详细+代码】(hoare+挖坑+前后指针)

常见排序

在这里插入图片描述

快速排序

hoare版

1、基本思想:任取待排序元素序列中的某元素作为基准值(key),按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2、过程
a. 单趟排序:(keyi在最左边)right先出发找小于a[keyi]值的值(找到后等待),left后出发找大于a[keyi]值的值, 然后Swap(a[lefti],a[right])。
b. 重复利用单趟排序,keyi左边的全是小于a[keyi]的,右边全是大于a[keyi]的。
c. 划分为[begin,keyi-1], keyi, [keyi+1,end],分别进行递归,类似二叉树的前序遍历。

在这里插入图片描述
3、时间复杂度 o ( n l o g n ) o(nlogn) o(nlogn)

部分代码实现

根据上方过程 写出一趟的排序代码:

while (left < right)
	{
		//右边开始行动   一定要加上等于,因为快速排序找的是一定比它小的值
		while (left < right && a[right] >= key)
		{
			right--;
		}
		//左边开始行动
		while (left < right &&   a[left] <= a[keyi])
		{
			left++;
		}
		swap(&a[left], &a[right]);
 
	}
	swap(&(a[keyi]), &(a[right]));

全部代码实现

将key放到合适的位置后(keyi的左边都小于key,keyi的右侧都大于key),将待排序列分为三个部分:[begin,keyi-1] keyi [keyi+1,end],分别递归进行排序。

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	int left = begin;
	int right = end;
	int keyi = left;
	while (left < right)//一趟的实现
	{
		//右边开始行动   一定要加上等于,因为快速排序找的是一定比它小的值
		while (left < right && a[right] >= key)
		{
			right--;
		}
		//左边开始行动
		while (left < right &&   a[left] <= a[keyi])
		{
			left++;
		}
		swap(&a[left], &a[right]);
 
	}
	swap(&(a[keyi]), &(a[right]));
	keyi = right;
	//[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi+1, end);
}

注意

  • 刚开始选择的基准值如果是左边第一个值,那么就应该右边right先出发一步步找到小于基准值keyi,这样可以保证最后left和right相遇的地方比基准值小(left和right相遇前,right 找到小于基准值的位置,等待left的靠近),交换后,保证了基准值的左边的值都小于基准值。反之亦然。

  • 在left和right向中间移动的过程中(以left为例):循环条件中必须保证:

    1. left<right 这样在每趟找值的过程中保证了right不会超过left甚至越界
    2. 当遇到和key值相等的值时left和right任然要继续走当right处的值小于(left遇到大于key的值)时才能停止 。否则遇到以下情况 ,while (left < right && a[keyi] <= a[right])和while (left < right && a[left] <= a[keyi])会陷入死循环。在这里插入图片描述

挖坑法

1、基本思想:挖坑法和hoare仅仅是理解方法上不一样,实际思路大致相同(某些单趟的后的结果可能不同)
2、过程
a. 单趟排序:先设置坑位(最左为例),用key保存最先坑位的值,right找到比key小的值后将值放入坑位,然后将此处置为新的坑,left也行动开始找值补坑,找到比key大的值将其放入坑位,置为新的坑
b. 重复利用单趟排序,当left与right相遇的时候,将key放入到坑位中,key左边的全是小于key的,右边全是大于key的。
c. 划分为[begin,hole_i-1], hole_i, [hole_i+1,end],分别进行递归,类似二叉树的前序遍历
在这里插入图片描述

部分代码实现

int key = a[begin];
while (left < right)//单趟
	{
		//右边找小于key的
		while (left < right && a[right] >= key)
		{
			--right;
		}
		a[hole_i] = a[right];
		hole_i = right;//新坑位
		
		//左边找大于key的
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[hole_i] = a[left];
		hole_i = left;//新坑位
	}
	a[hole_i] = key;//key的左边的值都小于key 右边都大于key

全部代码:

对区间分割,进行递归:
[begin, hole_i - 1] hole_i [hole_i + 1, end]

void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end) return;
	int left = begin;
	int right = end;
	int key = a[begin];
	int hole_i = begin;

	while (left < right)
	{
		//单趟

		//右边找小于key的
		while (left < right && a[right] >= key)
		{
			--right;
		}
		a[hole_i] = a[right];
		hole_i = right;//新坑位
		
		//左边找大于key的
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[hole_i] = a[left];
		hole_i = left;//新坑位
	}
	a[hole_i] = key;//key的左边的值都小于key 右边都大于key


	//[begin, hole_i - 1] hole_i [hole_i + 1, end]
	QuickSort2(a, begin, hole_i - 1);
	QuickSort2(a, hole_i + 1, end);
}

前后指针法

1、基本思想:需要两个指针,一个在前一个在后,分别用cur表示前指针,prev表示后指针,初始时,我们规定cur在prev的后一个位置,这里我们还是选择第一个数为基准值
2、过程:
a. cur位于begin+1的位置,prev位于begin位置,keyi先存放begin处的值。
b. cur不断往前+1,直到cur >= end时停止循环。
c. 如果cur处的值小于key处的值,并且prev+1 != cur,则与prev处的值进行交换。
d. 当循环结束时,将prev处的值与keyi的值相交换,并将其置为新的keyi位置。
在这里插入图片描述

部分代码:

	int key_i = begin;
	while (cur <= end)//单趟
	{
		if ((a[cur] < a[key_i])&&(++prev) != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[key_i],&a[prev]);
	key_i = prev;

全部代码:

//快速排序--Pointer
void QuickSort3(int* a, int begin, int end)
{
	if (begin >= end) return;
	int midi = GetMid(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int prev = begin;
	int cur = prev + 1;
	int key_i = begin;
	while (cur <= end)
	{
		//单趟
		if ((a[cur] < a[key_i])&&(++prev) != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[key_i],&a[prev]);
	key_i = prev;

	QuickSort3(a, begin, key_i - 1);
	QuickSort3(a, key_i+1, end);
}

优化

三数取中

如果取第一个元素为key 碰到特殊情况(第一个元素为最大或最小值)会降低效率 ,因此再第一个数、最后一个个数、和数组中随机一个数,这三个的中间值做为key可以避免特殊情况。

这个是取“中值”的代码:

int GetMid(int* a, int begin, int end)
{
	int mid = begin + (rand() % (end - begin));//[begin,end]中随机取值
	if (a[begin] > a[end])
	{
		if (a[end] > a[mid])
			return end;
		else if (a[mid] > a[begin])
			return begin;
		else
			return mid;
	}
	else
	{
		if (a[end] < a[mid])
			return end;
		else if (a[end] < a[begin])
			return begin;
		else
			return mid;
	}
}
小区间优化:

当递归到很小的区间(10个数据的区间)时 再采用递归会浪费不必要的时间和栈帧空间,因此当区间很小时 可以采用直接插入法进行排序,

//小区间用直接插入进行优化:当只有10个数以内 可以不需要递归
	
	if (end - begin + 1 <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值