有关快速排序的几点思考

快速排序曾被评为20世纪十大算法之一,最很多情况来说都是名副其实的最快的排序算法。其实思路并不难,但在具体的代码实现时会有很多细节容易出错。也有一些细节如果不加注意会使排序效率意外降低。

         大致思路是:从待排序数组中选出一个值(一般是随即选的),对数组进行分割,把小于轴值的放在左侧,大于轴值的放在右侧。然后分别对左侧数组和右侧数组继续进行排序(递归进行)。一直进行下去,最后可以得到一个有序数组。

         快速排序本质上是一种交换排序。我们最熟悉的交换排序可能应该是冒泡排序,但快速排序与冒泡排序最根本的不同是:它采用了分而治之的思想,不再是相邻的去进行交换。这样可以提高一些效率。

         几个需要注意的细节:

         1.轴值的选择

         最简单的选择当然是选择第一个或最后一个值,但这样有一个风险,如果数组原来是正序或逆序的,这样选择效率可能会比较低,因为有一侧的数组元素个数是0,这样最终其实是一个一个元素排的。当然可以用随机数确定下标,但比较麻烦。一般可以选择中间下标值。

         2.怎么分割数组

         最直接的方法是:确定轴值后,从左侧开始找到第一个大于轴值的元素,下标为l。从右侧开始找到第一个小于轴值的元素,下标为r。然后,直接交换两个值(l处和r处的)。交换后继续向前寻找。

         个人认为这样做比较麻烦,对边界处理比较麻烦,对轴值也要做处理。很容易出错。

         最常用的方法是:确定轴值后(一般是中间下标处的元素),设置一个遍历T parval=arr[mid](即是把轴值存起来)。然后把轴值和数组最后一个元素进行交换,这时相当于数组最后一个位置是空着的,等着放左侧符合条件的元素。然后从数组左侧开始遍历,找到第一个大于(也可以找大于等于的,但那样交换次数会多一些)轴值的元素,其下标为l,把其放在下标为r处(有的人实现时会判读一下lr的大小关系,如果l<r,r--。其实没有必要,不需要对r做处理,知识多使arr[r]parval多比较了一次而已)。这时再从下标为r处往左寻找,找到第一个小于轴值parval的值,放在下标l处。接着继续即可。知道lr相遇,返回l。几位轴值parval应该在的位置。

         代码如下:

//快速排序
#include <iostream>
using namespace std;
#define N 5

遇到两个值直接交换的
分割函数,得到分界点,左侧数据都小于分界点值,右侧的均大于
若使用arr[l]<=parval,arr[r]>parval这时对于分割值是最大值或最小值时会排序错误。比如5,4,10,3,9
此方法可能会陷入死循环,比如5,4,9,5
总之,这种方法需要对分割值做额外的处理,以下这种方式写肯定是有问题的
//template <class T>
//int Partion(T arr[],int left,int right)
//{
//	int l=left,r=right;
//	T parval=arr[(l+r)/2];//把中间位置的元素作为分界值,可以防止数组正序或逆序时的低效率
//	while(l<r)
//	{
//		while(l<r && arr[l]<parval)//找到左侧第一个不小于分界值的元素
//		{
//			l++;
//		}
//		while(l<r && arr[r]>parval)//找到右侧第一个不大于分界值的元素
//		{
//			r--;
//		}
//		//交换两个值
//			T temp=arr[l];
//			arr[l]=arr[r];
//			arr[r]=temp;	
//			cout<<"haha"<<endl;
//	}
//	cout<<l<<endl;
//	return l;
//}


不直接进行交换的
template <class T>
int Partion(T arr[],int left,int right)
{
	int l=left,r=right;
	int mid=(l+r)/2;	//数组的中间下标

	//把中间位置的元素作为分界值,可以防止数组正序或逆序时的低效率
	//分割前先交换arr[mid](分割值)和arr[r](最右侧元素)
	T temp=arr[mid];
	arr[mid]=arr[r];	
	arr[r]=temp;	

	//最右侧元素(即交换前的中间位置的元素值)
	//如果是把最左侧元素作为中间值(即之前把arr[l]与arr[mid]交换),那么while循环内从右侧开始即可。效果是一样的。
	T parval=arr[r];

	while(l<r)
	{
		while(l<r && arr[l]<=parval)//找到左侧第一个大于分界值的元素
		{
			l++;
		}
		//if(l<r)
		//{
		//	arr[r]=arr[l];	//把arr[l](即是左侧第一个大于分界值的元素)放置arr[r]处,这是arr[l]空了下来
		//	r--;	//r左移一位
		//}
		//或者是不进行判定l和r的大小,直接不移动r,充其量是多把arr[r]与parval比较了一次
		arr[r]=arr[l];

		while(l<r && arr[r]>=parval)//找到右侧第一个小于分界值的元素
		{
			r--;
		}
		//if(l<r)
		//{
		//	arr[l]=arr[r];	//把arr[r](即是右侧第一个大于分界值的元素)放置arr[l]处,这是arr[r]空了下来
		//	l++;	//l右移一位
		//}
		//或者是不进行判定l和r的大小,直接不移动l,充其量是多把arr[l]与parval比较了一次
		arr[l]=arr[r];
	}
	
	//非常关键的一步,要把分界值放置最后空闲的位置处(arr[l])
	arr[l]=parval;

	//返回分界值的最终下标
	return l;
}


//快速排序(归并)
template <class T>
void QuickSort(T arr[],int left,int right)
{
	if(left>=right)return;//递归出口

	int pivot=Partion(arr,left,right);
	QuickSort(arr,left,pivot-1);
	QuickSort(arr,pivot+1,right);
}

//输出数组内容
template <class T>
void print(T arr[],int size)
{
	for(int i=0;i<size;i++)
	{
		cout<<arr[i]<<"  ";
	}
	cout<<endl;
}

//主函数
int main()
{
	//int arr[N]={5,4,1,3,9,12,7,22,0,-10,12,33,-22,0,88,123,-45,345,-98,-666};
	int arr[N]={5,4,10,5,9};
	//int arr[N]={5,4,0,3,9,22,7,22,0,-10};

	cout<<"原始数据为:"<<endl;
	print(arr,N);

	QuickSort(arr,0,N-1);

	cout<<"排序后:"<<endl;
	print(arr,N);

	return 0;
}

第一个分割方法(即直接交换的,一直没有通过所有测试用例),希望能有高手给知道一下。

注意测试用例的全面性:比如一定要测试那些含有重复元素的数组、分割值即是最大值或最小值的数组。很容易发现bug



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值