快速排序及其实现

前言:

快速排序有很多种实现方法,但核心思想都是一样的:

思想:

1.选择基准值。

从序列中选择一个数当做基准值,选择放法很多,比如取第一个、最后一个或中间的值。

2.划分操作

将序列中所有比基准值小的数移动到基准值左边,比基准值大的移动到基准值的右边。

在这步操作结束后,基准值就已经排好在最终的位置上了。

3.递归以上操作

接着将基准值两边的序列进行递归排序,最终完成排序。

示例(双指针法):

代码:

//双指针法
void qqsort1(int*a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int tem = left;
	int cur = left+1;
	int key = a[tem];

	while (cur <= right)
	{
		if (a[cur] < key)
		{
			++tem;
			swap(&a[cur], &a[tem]);
		}
		cur++;
	}
	swap(&a[tem], &a[left]);
	qqsort1(a, left, tem - 1);
	qqsort1(a, tem + 1, right);
}

代码流程:

用tem和cur标定比基准值小的和目前的排序目标,进行比对,小的放在序列左边,大的放在左边小序列的右边,因为选取的是用第一个数作为基准值,所以先不移动基准值。排好序后,将tem指向的数:也就是比基准值小的最右边的数,和基准值left交换,这样就完成了基准值左边得数都比他小,右边的数都比他大的操作了。

示例(hoare法):

代码:

int MidIndex(int* a, int left, int right)
{
	int mid = (left + right + 1) / 2;
	if (a[left] < a[right])
	{
		if (a[left] > a[mid])
		{
			return left;
		}
		else if (a[right] < a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
	else//a[left] > a[right]
	{
		if (a[left] < a[mid])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}
//hoare法
int qqsort2(int* a, int left, int right)
{
	int mid = MidIndex(a, left, right);
	swap(&a[left], &a[mid]);

	int keyi = left;
	while (left < right)
	{
		while (left < right)
		{
			if (a[right] < a[keyi])
			{
				break;
			}
			right--;
		}
		while (left < right)
		{
			if (a[left] > a[keyi])
			{
				break;
			}
			left++;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[keyi], &a[left]);
	return left;
}

代码流程:

一、  MidIndex函数是用来取序列左、右和中间位置的中间大小元素(三数取中)。以求一个平衡,防止特殊数据导致算法性能下降。

二、 hoare算法和双指针操作有所不同,不采用递归策略,而是对数组左右两边进行操作。

1.在找到大致的中间数a[mid]后,先将mid放在数组左边,然后left指向mid+1,right指向数组最右边

2.两指针从左右相向而行,分别在找到不符合条件时停下左指针遇到大于a[mid]的数,右指针遇到小于a[mid]的数。 

这时说明两指针指向的数不符合快排核心思想:将小于基准值的值放在左边,大于基准值的放在右边

3.交换两指针指向的数,循环直到两指针相遇;最后将存在这段数组左边的基准值a[mid]放在正确的位置a[left],因为这是两个指针相遇的地方。

4.返回基准值所在的下标,和双指针法一样,这时基准值已经排在了正确的位置上,接下来递归调用hoare方法传入基准值的左右区间,就可以将数组整个排序。

示例(挖坑法)

代码:

int qqsort3(int* a, int left, int right)
{
	int mid = MidIndex(a, left, right);
	swap(&a[left], &a[mid]);
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (left < right)
		{
			if (a[right] < key)
			{
				a[hole] = a[right];
				hole = right;
				break;
			}
			right--;
		}
		while (left < right)
		{
			if (a[left] > key)
			{
				a[hole] = a[left];
				hole = left;
				break;
			}
			left++;
		}

	}
	a[hole] = key;
	return hole;
	 
	 
}

代码流程:

一、 首先和hoare法一样,先通过三数取中找到一个合适的基准值,然后将基准值移到序列左侧。

不同的是,需要将基准值事先保存好。并且设置一个“坑”, 当前坑的位置在基准值的位置。

二、仍然是左右两个指针,分别从两边遍历,在找到不符合规则的数时,将这个数放在坑的位置,然后另一边的指针开始遍历,直到两指针相遇。

三、这时坑的两边就已经符合了左边小于基准值,右边大于基准值。所以坑的位置刚好是基准值所在的位置,将基准值放在坑的位置即可。

四、接着递归基准值两边的序列即可完成整个数组的排序。

示例(挖坑法非递归实现):

代码:

非递归需要借助栈来实现,简单给出栈的结构:

typedef int StackData;

typedef struct Stack 
{
	StackData* a;
	int top;
	int capacity;
}ST;
void quecksortnon(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, left);
	STPush(&st, right);
	while (!STEmpty(&st))
	{
		int right = STTop(&st);
		STPop(&st);
		int left = STTop(&st);
		STPop(&st);
		int keyi = qqsort3(a, left, right);

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

	STDestroy(&st);
}

代码流程:

一、定义出栈且初始化,将待排序的序列边界压入栈中。

二、进入循环,取出压入栈中的栈顶的两个元素,代表目前要排序的区间。

三、利用挖坑法将该区间的基准值排好序,返回基准值的位置。

四、检查返回的基准值两边区间是否有剩余(即查看基准是是否是边界),如果有就压入栈,代表有区间没有完成排序。

五、循环完成操作,直到栈空就说明全部区间都排序完成。

非递归的优点:

它避免了函数调用产生的开销,尤其是在排序非常大的数组时

算法复杂度:

快排是一种不稳定的算法(相同数的相对位置发生变化),要看情况而定。

在最好的情况下:为O(n log N)

就是说每次分割都是将序列平均分为两半

正常情况下:为O(n log N)

因为我们有方法可以控制基准值的取值,利用三数取中可以较为平衡的找到靠近中间的数。

最坏情况下:O(n^2)

在某些特殊场景中,序列每次取基准值都是左边一个,右边n-1个,划分了n-1次,排序的时间复杂度也就升维变成了O(n^2)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值