八大排序之快速排序

快速排序

快排基本思想

通过一趟排序将待排序的数据分为两个部分,一部分的数据小于另一部分,再利用递归对两部分的数据进行快速排序,最终使整个序列变为有序。
具体步骤:

  1. 首先,挑选数据中某个数据作为基准数。
  2. 将数据分区,比基准数大的数据放在右边,小于基准书的数据放在左边。
  3. 再对左右分区进行快速排序,直到各区只有一个值。

三种不同的快排思路

a.固定基准数法

取首个元素或最后一个元素作为基准数。
法11
此时取33作为基准数,left指向数据头,right指向数据尾。
法12
right找小于基准数的值,left找大于基准数的值,找到后交换两值。需要注意的是,当选择数据头做基准数时,要先移动right指针扫描,而选择数据尾值做基准数时,先移动left指针扫描。
fa13
重复上述步骤,法14
法15
当左右指针同时指向某元素时,交换基准数与当前指向位置的值,单趟排序结束。再将基准数左边和右边的数据分别快排递归即可。
法16

b.挖坑法

挖坑法是将数据第一个值看做基准数,将此元素拿出数组,此时他所在的位置形成一个坑位,从后向扫描寻找小于基准数的值,直接放入坑位,形成新的坑,再从前向后扫描寻找大于基准数的值,放入当前坑位,形成新坑,如此往复,在最后的坑位中填入基准数。
法2

c.指针法

最右侧数据为key。
定义cur和prev指针指向最左边,cur向后寻找小于key的值。
找到后和prev交换,prev向后移动一位。
最后将key所在的位置和prev最后停下的位置交换。
在这里插入图片描述
由于找不到比3小的值,prev(33)和key(3)交换。
再对{22,54,7,79,17,25,33}进行递归快排。
在这里插入图片描述
再重复对左右取件递归,完成排序。

快排的效率

在最传统的快排基准数方法中,情况最佳下复杂度为O(nlogn),但当数组有序时,此种方法下,快排效率将退化到冒泡排序即O(N^2)。

快排的几种优化

1.当数据长度满足某情况时,使用插入排序(小区间优化)。

当快速排序的递归深度较深时候,划分区间较小,此时使用快速排序的话,效率较差,所以在较深程度下,使用插入排序更优。

void QuickSort(int* a, int left,int right)
{
	if (left >= right)
	{
		return;
	}
	// 小区间优化
	if (right - left + 1 < 10)
	{
		InsertSort(a+left, right - left + 1);
	}
	else
	{
		int keyi = Partition(a, left, right);
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
}

2.三数取中

当数据为逆序时,基准数选择为最大或最小值,快速排序效率较差,此时可以将基准数换为数组中间位置的值。

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	// int mid = left + (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

代码实现

固定基准数法:

int Partition(int* a, int left, int right)
{
	assert(a);
	int mini = GetMidIndex(a, left, right);
	swap(a[mini], a[left]);
	// 三数取中,防止递归栈溢出
	int keyi = mini;
	while (left < right)
	{
		// 右边先走,找小
		while (a[right] >= a[keyi] && left < right)
		{
			--right;
		}
		// 左边再走,找大
		while (a[left] <= a[keyi] && left < right)
		{
			++left;
		}
		swap(&a[left], &a[right]);
	}
	swap(&a[left], a[keyi]);
	return left;
}

挖坑法:

int Partion2(int* a, int left, int right)
{
	int key = a[left];
	int begin = left, end = right;
	while (begin < end)
	{
		while (a[end] >= key && begin < end)
		{
			end--;
		}
		a[begin] = a[end];
		while (a[begin] <= key && begin < end)
		{
			begin++;
		}
		a[end] = a[begin];
	}
	a[begin] = key;
	return begin;
}

指针法

int Partion3(int* a, int left, int right)
{
	int keyi = right;
	int prev = left;
	int cur = left;
	while (cur <= right)
	{
		if (a[cur] <= a[keyi] && prev != cur) // 防止自身交换
		{
			prev++;
			swap(&a[cur], &a[prev]);
		}
		// 无论cur是否找到了比key小的数据,cur都要继续向下寻找
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	return prev;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值