快速排序:快速排序是对冒泡排序的一种改进,由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);
稳定性:不稳定
如图: