快速排序

快速排序:快速排序是对冒泡排序的一种改进,由C.A.R.Hoare在1962年提出。快速排序的基本思想是:先找到一个基准值key,(至于这个key怎么找,我在后面会列出来)。通过一趟排序把待排序序列分为两部分,左边一部分为小于基准值的元素,右边一部分为大于基准值的元素。然后再循环分别对这两部分进行取基准值并排序,所以快速排序实际上是一个递归的过程,可以以此达到使整个序列变为有序序列。

我来画一个图理解直观的理解一下快速排序:

上述为快速排序的基本过程,代码实现如下:

void Swap(int *x,int *y)
{
	*x^=*y;
	*y^=*x;
	*x^=*y;
}

void PrintSort(int arr[],size_t size)
{
	size_t i=0;
	for(; i<size; ++i)
	{
		printf(" %d ",arr[i]);
	}
	printf("\n");
}
int Partion(int arr[],int beg,int end)
{
	int left=beg;
	int right=end-1;
	int key=arr[right];
	while(left<right)
	{
		//从左到右找到一个大于基准值的元素
		while(left<right&&arr[left]<=key)
		{
			left++;
		}
		//从右到左找到一个小于基准值的元素
		while(left<right&&arr[right]>=key)
		{
			right--;
		}
		if(left<right)
		{
			Swap(&arr[left],&arr[right]);
		}
	}
	Swap(&arr[left],&arr[end-1]);//将left值和基准值进行交换,left指向的值一定大于基准值,图上已经解释过
	return left;
}
void _QuickSort(int arr[],int beg,int end)
{
	int mid;
	if(end-beg<=1)
	{
		return;
	}
	mid=Partion(arr,beg,end);//通过找到的mid对当前的[beg,end)区间进行调整
	_QuickSort(arr,beg,mid);
	_QuickSort(arr,mid+1,end);
}
void QuickSort(int arr[],size_t size)
{
	if(size<=1)
	{
		return;
	}
	_QuickSort(arr,0,size);
}
int main()
{
	int arr[]={5,7,2,9,0,12,6};
	size_t size=sizeof(arr)/sizeof(arr[0]);
	QuickSort(arr,size);
	PrintSort(arr,size);
	system("pause");
	return 0;
}

上述代码的Partion函数是通过交换法实现的。我们还有两种方法实现Partion函数:分别为挖坑法和双指针前移法。

挖坑法:

话不多说,看图:

 

//挖坑法
int Partion1(int arr[],int beg,int end)
{
	int left=beg;
	int right=end-1;
	int key=arr[right];//以最后一个元素作为基准值,此时最后元素的位置就是一个坑,可以被覆盖
	while(left<right)
	{
		// 从左到右找到一个大于基准值的元素
		while(left<right&&arr[left]<=key)
		{
			++left;
		}
		if(left<right)
		{
			arr[right--]=arr[left];//把大于基准值的元素放在前面挖的坑里,此时left位置又变成一个坑
		}
		//从右到左找到一个小于基准值的元素
		while(left<right&&arr[right]>=key)
		{
			--right;
		}
		if(left<right)
		{
			arr[left++]=arr[right];//把找到的小于基准值的元素放在前面Left的坑里,right又变成坑
		}
	}
	arr[right]=key;//此时left==right,并且将key填到right的坑里
	return left;
}

最后一种双指针前移法,可谓是666呀,代码简洁。过程如下:

 

双指针前移法算法思想我们可以这样理解:j就是让cur找到一个小于key的值,pre由于在cur之前,所以pre指向的元素肯定大于key,然后交换cur和pre所指向的值,不断的把小于key的值给前挪,从而完成排序。

//双指针前移法
int Partion2(int arr[],int beg,int end)
{
	int cur=beg;
	int pre=beg-1;
	int key=arr[end-1];
	while(cur<end)
	{
		if(arr[cur]<key&&++pre!=cur)
		{
		  //把大于基准值的元素往后挪,把小于基准值的元素往前挪
			Swap(&arr[cur],&arr[pre]);
		}
		   ++cur;
	}
	if(++pre!=end)
	{
		Swap(&arr[pre],&arr[end-1]);
	}
	return pre;
}

前面说的这些都是以最右边元素为基准值,为了使快速排序更加的高效,我们可以进行优化:

1.三值取中定基准值,所谓三值取中,就是取出第一个元素,中间元素,以及最后一个元素,然后选出这三个元素中的中间元素作为基准值key,

void Swap(int *x,int *y)
{
	*x^=*y;
	*y^=*x;
	*x^=*y;
}

void PrintSort(int arr[],size_t size)
{
	size_t i=0;
	for(; i<size; ++i)
	{
		printf(" %d ",arr[i]);
	}
	printf("\n");
}
int Partion(int arr[],int beg,int end)
{
	int left=beg;
	int right=end;
	int key=arr[right];
	while(left<right)
	{
		//从左到右找到一个大于基准值的元素
		while(left<right&&arr[left]<=key)
		{
			left++;
		}
		//从右到左找到一个小于基准值的元素
		while(left<right&&arr[right]>=key)
		{
			right--;
		}
		if(left<right)
		{
			Swap(&arr[left],&arr[right]);
		}
	}
	Swap(&arr[left],&arr[end]);//将left值和基准值进行交换,left指向的值一定大于基准值,图上已经解释过
	return left;
}
void MidThreeNum(int arr[],int beg,int end)
{
	int mid=beg+(end-beg)/2;
	if(arr[mid]<arr[beg])
	{
		Swap(&arr[mid],&arr[beg]);
	}
	if(arr[mid]>arr[end])
	{
		Swap(&arr[mid],&arr[end]);
	}
	if(arr[mid]<arr[beg])
	{
		Swap(&arr[mid],&arr[beg]);
	}
	Swap(&arr[mid],&arr[end]);
}

void _Quick2(int arr[],int beg,int end)
{
	int mid;
	int left;
	int right;
	MidThreeNum(arr,beg,end);
	mid=Partion(arr,beg,end);
	left=mid-1;
	right=mid+1;
	if(mid>beg+1)
	{
		_Quick2(arr,beg,mid-1);
	}
	if(mid<end-1)
	{
		_Quick2(arr,mid+1,end);
	}
}

void Quick2(int arr[],size_t size)
{
	if(size<=1)
	{
		return;
	}
	_Quick2(arr,0,size-1);
}
int main()
{
	int arr[]={23,554,65,7,68,7,9,89};
	size_t size=sizeof(arr)/sizeof(arr[0]);
	Quick2(arr,size);
	PrintSort(arr,size);
	system("pause");
	return 0;
}

2.随机基准法,从序列中随机选出一个元素作为基准值,也可以从一定程度上优化我们的快排。

3.基准聚焦法,当遇到与基准值相同的元素时,不再对它进行比较,可以将它靠近基准值的旁边。

4.当区间比较小的时候,可以使用插入排序,直接对这个区间进行排序,从而减少递归次数。

5.当递归深度 达到一定程度的时候,使用堆排序对待排序进行排序。

快速排序的非递归代码如下:

//非递归快速排序
/
void  QuickSortByLoop(int arr[],int64_t size)
{
	SeqStack stack;
	int64_t beg;
	int64_t end;
	int64_t mid;
	SeqStackInit(&stack);
	 if(size<=1)
	{
		return;
	}
	beg=0;
	end=size-1;
	
	if(beg<end)
	{
	   mid=Partion3(arr,beg,end);
	   if(beg<mid-1)
	   {
	      SeqStackPush(&stack,beg);
	      SeqStackPush(&stack,mid-1);
	   }
	   if(mid+1<end)
	  {
		 SeqStackPush(&stack,mid+1);
	     SeqStackPush(&stack,end);
	  }

	  while(stack.size>0)
	 {
		int value1=0;
	    int   value2=0;
    	SeqStackTop(&stack,&value1);
		SeqStackPop(&stack);
		SeqStackTop(&stack,&value2);
		SeqStackPop(&stack);
		mid=Partion3(arr,value2,value1);
		if(value2<mid-1)
		{
		  SeqStackPush(&stack,value2);
		  SeqStackPush(&stack,mid-1);
		}
		if(mid+1<value1)
		{
 		   SeqStackPush(&stack,mid+1);
		   SeqStackPush(&stack,value1);
		}
     }
	}

}

算法分析:

时间复杂度:最坏情况下(序列完全逆序)为O(N^2),平均时间复杂度为:O(N*logN);

空间复杂度:进行二分的递归操作,故空间复杂度为:O(logN);

稳定性:不稳定

如图:

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值