排序算法三----快速排序

一、快速排序

1、取序列里的任意位置作为基准值;
2、通过一种方法,将序列分为左右两部分,左边的元素比基准值小,右边的元素比基准值大。基准值位于中间;
3、此时左右部分均无序,在左边部分取基准值,并且把左部分划分为两部分,右边同理,循环执行
//分割的方式
int Partion(int *array, int left, int right)
{
	//取基准值
	int key = array[right - 1];
	int begin = left, end = right-1;
	while (begin < end)//区间内还有元素
	{
		//从前往后找,找比基准值大的元素
		while (begin<end&&array[begin] <= key)
			++begin;
		//从后往前找,找比基准值小的元素
		while (begin<end&&array[end] >= key)
			--end;
		if (begin<end)
		//交换begin和end的位置
		  Swap(&array[begin], &array[end]);
	}
	if (begin != right - 1)//begin和end不是同一个位置
		Swap(&array[begin], &array[right - 1]);
	return begin;
}
void QuickSort(int *array, int left, int right)
{
	//只剩一个元素不需要排序
	int div = 0;
	if (right - left > 1)
	{
		//分割,返回基准值的下标
		div=Partion(array, left, right);
		//对左半边进行排序
		QuickSort(array, left, div);
		//对右半边进行排序
		QuickSort(array, div+1, right);
	}
}


分割基准值的方法
法一:挖坑法:
1、取序列里的任意位置作为基准值(如取最右边的的元素);将序列分为左右两部分,左边的元素比基准值小,右边的元素比基准值大。
2、将基准值的值移走,坑还在。从前往后找,找比基准值大的元素,用这个元素的值填充基准值的坑,在此处形成一个坑。从后往前找,找比基准值小的元素
,用此处的元素填充上一个(左边的坑begin),此处形成一个坑……begin向后走(或者end往前走)循环进行……用基准值填充最后剩下的那一个坑。
//挖坑法
int Partion1(int *array, int left, int right)
{
	//取基准值
	int key = array[right - 1];
	int begin = left, end = right - 1;
	while (begin < end)//区间内还有元素
	{
		//从前往后找,找比基准值大的元素
		while (begin<end&&array[begin] <= key)
			++begin;
		if (begin < end)
		{
			array[end] = array[begin];
			--end;
		}
		//从后往前找,找比基准值小的元素
		while (begin<end&&array[end] >= key)
			--end;
		if (begin < end)
		{
			array[begin] = array[end];//上一个坑在begin的位置上
			++begin;
		}
	}
	array[begin] = key;
	return begin;
}
void QuickSort(int *array, int left, int right)
{
	//只剩一个元素不需要排序
	int div = 0;
	if (right - left > 1)
	{
		//分割,返回基准值的下标
		div=Partion1(array, left, right);
		//对左半边进行排序
		QuickSort(array, left, div);
		//对右半边进行排序
		QuickSort(array, div+1, right);
	}
}
法二:前后指针:
1、取基准值,
2、从cur的位置开始找,找比基准值小的元素,找到后pre向前走一步,若是pre向前走一步后与cur相遇,cur向后走一步,不进行其他操作;若是找到比基准值大的元素,cur向后走一步,pre不向后走,而且不进行其他操作。
,若是pre向前走一步后与cur不相遇,交换cur和pre的值(数据,不交换位置),cur朝后走一步。重复进行越界后,交换cur与pre++的值,因为pre++后的值比基准值大。
int Partion2(int *array, int left, int right)
{
	int cur = left, pre = cur-1;
	int key = right - 1;//取基准值为最右边的元素
	while (cur < right)
	{
		if (array[cur] < key&&++pre != cur)
			Swap(&array[cur], &array[pre]);
		cur++;
	}
	if (++pre != right)
		Swap(&array[pre], &array[right - 1]);
	return pre;
}
void QuickSort(int *array, int left, int right)
{
	//只剩一个元素不需要排序
	int div = 0;
	if (right - left > 1)
	{
		//分割,返回基准值的下标
		div=Partion2(array, left, right);
		//对左半边进行排序
		QuickSort(array, left, div);
		//对右半边进行排序
		QuickSort(array, div+1, right);
	}
}
 
快速排序的时间复杂度:单次划分的时间复杂度为O(n),最差情况下(给定的序列接近有序)O(n^2),效率很差;最优情况下,每次取一个元素都可以将序列分为相等的两部分:比较n次,所以有n层。第一层,O(n),第二层2,2*O(n/2),第三层,3*O(n/3)……
第n层,n*O(n/n)==o(n)最后计算得:O(n*lgN):每层比较次数为n,划分为lgN层(深度)。
快速排序的空间复杂度:递归的深度(树的高度n):O(lgN)
快速排序的稳定性:不稳定,跨区间交换元素
避免选中的基准值正好为最大或者最小值,不适用于近似有序的序列。
快排适用于很随机的数据的排序。
二、快速排序的优化:
1、基准值的选取:
(1)缺点:选中的基准值可能正好为最大或者最小值,应避免取到最大值或者最小值。若是基准值为最小值,数据全部都会位于基准值的右边,而基准值为最大值,数据全部位于基准值的左边。但是我们选取基准值是为了将数据分为左右两部分。
若元素是有序的,排序后类似于单只树,使效率低下
优化:不取最大值或最小值作为基准值,不从最右侧开始取基准值,一次取三个值,取这三个值中间数据作为基准值。降低取到最大值或最小值作为基准值的概率。
int GetMiddleIndex(int *array, int left, int right)
{
	int mid = left + ((right - left) >> 1);
	if (array[left] < array[right])//左边小于右边
	{
		if (array[mid] < array[left])//中间数据小于最小的元素
			return left;
		else if (array[mid]>array[right])//中间数据大于最大的元素
			return right;
		else//位于中间数据
			return mid;
	}
	else
	{
		if (array[mid] > array[left])//中间数据大于最大的数据
			return left;
		else if (array[mid]<array[right])//中间数据小于最小的数据
			return right;
		else
			return mid;
	}
}
//分割的方式
int Partion(int *array, int left, int right)
{
	//取基准值
	int key = 0;
	int begin = left, end = right - 1;
	int midIndex = GetMiddleIndex(array, left, right - 1);
	//把中间值换到最右侧
	Swap(&array[midIndex], &array[end]);
	key = array[end];
	while (begin < end)//区间内还有元素
	{
		//从前往后找,找比基准值大的元素
		while (begin<end&&array[begin] <= key)
			++begin;
		//从后往前找,找比基准值小的元素
		while (begin<end&&array[end] >= key)
			--end;
		if (begin<end)
			//交换begin和end的位置
			Swap(&array[begin], &array[end]);
	}
	if (begin != right - 1)//begin和end不是同一个位置
		Swap(&array[begin], &array[right - 1]);
	return begin;
}
int Partion1(int *array, int left, int right)
{
	//取基准值
	//找中间值,即基准值的位置
	int key = 0;
	int begin = left, end = right - 1;
	int midIndex = GetMiddleIndex(array, left, right - 1);
	//把中间值换到最右侧
	Swap(&array[midIndex], &array[end]);
	key = array[end];
	while (begin < end)//区间内还有元素
	{
		//从前往后找,找比基准值大的元素
		while (begin<end&&array[begin] <= key)
			++begin;
		if (begin < end)
		{
			array[end] = array[begin];
			--end;
		}
		//从后往前找,找比基准值小的元素
		while (begin<end&&array[end] >= key)
			--end;
		if (begin < end)
		{
			array[begin] = array[end];//上一个坑在begin的位置上
			++begin;
		}
	}
	array[begin] = key;
	return begin;
}
int Partion2(int *array, int left, int right)
{
	int cur = left, pre = cur - 1;
	int key = right - 1;//取基准值为最右边的元素
	int end = right - 1;
	int midIndex = GetMiddleIndex(array, left, right - 1);
	//把中间值换到最右侧
	Swap(&array[midIndex], &array[end]);
	key = array[end];
	while (cur < right)
	{
		if (array[cur] < key&&++pre != cur)
			Swap(&array[cur], &array[pre]);
		cur++;
	}
	if (++pre != right)
		Swap(&array[pre], &array[right - 1]);
	return pre;
}
void QuickSort(int *array, int left, int right)
{
	//只剩一个元素不需要排序
	int div = 0;
	if (right - left > 1)
	{
		//分割,返回基准值的下标
		div=Partion2(array, left, right);
		//对左半边进行排序
		QuickSort(array, left, div);
		//对右半边进行排序
		QuickSort(array, div+1, right);
	}
}

(2)插入排序适用于数据量小的,而快速排序则不适用。我们可以先判断是不是数据量很小,再选择排序的方法
void QuickSort(int *array, int left, int right)
{
	//只剩一个元素不需要排序
	int div = 0;
	if (right - left<16)
		InsertSort(array, right - left);
	else	{
		//分割,返回基准值的下标
		div=Partion2(array, left, right);
		//对左半边进行排序
		QuickSort(array, left, div);
		//对右半边进行排序
		QuickSort(array, div+1, right);
	}
}
 划分的函数 Partion2(array, left, right)同上
2、将递归的排序转换为循环的排序(用栈实现)

 划分的函数 Partion2(array, left, right) 同上
void QuickSortNor(int *array, int size)
{
	int div = 0;
	Stack s;
	StackInit(&s);
	//栈是先进后出的,先放右边界,则先取出左边界
	StackPush(&s, size);
	StackPush(&s, 0);
	while (!Stackempty(&s))
	{
		int left = 0;
		int right = 0;
		//先取左边界
		left = StackTop(&s);
		//将左边界拿出来
		StackPop(&s);
		//取右边界
		right = StackTop(&s);
		//将右边界拿出来
		StackPop(&s);
		if (left < right)//为有效边界
		{
			//划分
			div = Partion2(array, left, right);
			//先处理左半部分,则先将右半部分压栈
			StackPush(&s, right);//右半部分的右边界
			StackPush(&s, div + 1);//右半部分的左边界
			StackPush(&s, div);//左半部分的右边界
			StackPush(&s, left);//左半部分的左边界
		}
	}
}



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuruhua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值