🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
今天带来的内容是快速排序
这里是下面要讲的知识内容🥳🥳🥳
文章目录
一、⚽快速排序(原始方法)
1.🌈单趟代码与解释(升序)
选出一个key,一般是最左边或者最右边的数据,定义两个指针,左指针从左开始走,左指针找大,右指针从右开始走,右指针找小,然后Swap左指针和右指针的数据,当左指针和右指针相遇的时候,Swap key和该点的数据
保证在一趟走完之后,key的左边比key小,右边比key大
//单趟代码
void QuickSort(int *a,int n)
{
int key=0;
int left=0,right=n-1;
while(left<rigth)
{
while(left<right&&a[right]>a[key])
right--;
while(left<right&&a[left]<a[key])
left++;
Swap(&a[left],&a[right]);
}
Swap(a[key],a[left]);
}
我们必须保证right要先走,解释如下:
right先走,到最后,right停下了之后,left++去碰right,因为right找到的是小的数据,如果说让left先走,这种情况下最后停下来的是比key大的数据
2.🌈递归完整代码&&时间复杂度分析
void QuickSort(int*a,int begin,int end)
{
if()
return ;
int left=begin;
int right=end;
int keyi=left;
while(left<rigth)
{
while(left<right&&a[right]>a[keyi])
right--;
while(left<right&&a[left]<a[keyi])
left++;
Swap(&a[left],&a[right]);
}
Swap(a[keyi],a[left]);
keyi=left;
QuickSort(a,begin,keyi-1);
QuickSort(a,keyi+1,end);
}
时间复杂度O(NlogN)
最坏的情况是有序或者接近有序,O(N^2),N+N-1+N-2+…
二、⚽快速排序(挖坑法)
按升序来说
右边找小,填到左边的坑,这个位置成为新的坑,然后左边找大,填到这个坑当中
int PartSort(int *a,int begin,int end)
{
int key=a[begin];
int piti=begin;
while(begin<end)
{
while(begin<end&&a[end]>=key)
--end;
a[piti]=a[end];
piti=end;
while(begin<end&&a[begin<=key])
++begin;
a[piti]=a[begin];
piti=begin;
}
a[piti]=key;
return piti;
}
void QuickSort(int *a,int begin,int end)
{
if(begin>=end)
return ;
int keyi=PartSort(a,begin,end);
QuickSort(a,begin,keyi-1);
QuickSort(a,keyi+1,end);
}
三、⚽快速排序(前后指针版本)优
思路就是prev指向更新数据的下一个,cur去找比key小的数据(升序)
int PartSort(int *a,int begin,int end)
{
int prev,cur,key;
prev=key=begin;
cur=begin+1;
while(cur<=end)
{
if(a[cur]<a[key])
{
++prev;
Swap(&a[cur],&a[prev]);
}
++cur;
}
Swap(&a[prev],&a[key]);
return prev;
}
void QuickSort(int *a,int begin,int end)
{
if(begin>=end)
return ;
int keyi=PartSort(a,begin,end);
QuickSort(a,begin,keyi-1);
QuickSort(a,keyi+1,end);
}
四、⚽快速排序的优化
可以看出key的取值是很影响QuickSort的效率的,因此,我们需要对快速排序的算法进行优化
1、🌈随机选key
随便选一个当做key,可能选到最小的或者最大的那一个,不够好,因为快速排序最理想的就是二分,选到最中间的那个数。所以这种就不进行深度探索了
2、🌈三数取中
****
void GetMidIndex(int*a,int begin,int end)
{
int mid=(begin+end)/2;
if(a[begin]<a[end])
{
if(a[mid]>a[end])
return end;
else if(a[mid]<a[begin])
return begin;
else
return mid;
}
else
{
if(a[mid]>begin)
return begin;
else if(a[mid]<a[end])
return end;
else
return mid;
}
//找到了之后和begin交换以下即可作为keyi
}
3、🌈处理小区间
因为递归是个很麻烦的事,当递归划分小区间,区间比较小的时候,就不再递归划分去排序这个小区间,可以考虑用其他排序对小区间处理,比如插入排序
这样做减少了非常多的递归次数,最后一层就占了总的递归次数的一半,如果能把最后3、4层都去掉,至少减掉了过半的递归次数(可以假设区间小于10就不再递归)
代码:
void QuickSort(int *a,int begin,int end)
{
if(begin>end)
{
return ;
}
if(end - begin>10)
{
//快排
}
else
{
//其他几种排序算法
}
}
4、🌈最终优化代码(没有加上小区间处理)
void GetMidIndex(int*a,int begin,int end)
{
int mid=(begin+end)/2;
if(a[begin]<a[end])
{
if(a[mid]>a[end])
return end;
else if(a[mid]<a[begin])
return begin;
else
return mid;
}
else
{
if(a[mid]>begin)
return begin;
else if(a[mid]<a[end])
return end;
else
return mid;
}
//找到了之后和begin交换以下即可作为keyi
}
int PartSort(int *a,int begin,int end)
{
int prev,cur,key;
prev=key=begin;
cur=begin+1;
int midi=GetMidIndex(a,begin,end);
Swap(&a[midi],&a[begin]);
while(cur<=end)
{
if(a[cur]<a[key])
{
++prev;
Swap(&a[cur],&a[prev]);
}
++cur;
}
Swap(&a[prev],&a[key]);
return prev;
}
void QuickSort(int *a,int begin,int end)
{
if(begin>=end)
return ;
int keyi=PartSort(a,begin,end);
QuickSort(a,begin,keyi-1);
QuickSort(a,keyi+1,end);
}
五、⚽快速排序非递归
递归的大问题就是,极端场景下,深度太深,会栈溢出
1.直接改循环,比如斐void波那契数列、归并排序
2.用数据结构栈模拟递归过程
代码:
void QuickSortNonR(int*a,int begin,int end)
{
ST st;
StackInit(&st);
StackPush(&st,end);
StackPush(&st,begin);
while(!StackEmpty(&st))
{
int left = StackTop(&st);
StacckPop(&st);
int right = StackTop(&st);
StacckPop(&st);
int keyi=PartSort(a,left,right);
if(right>keyi+1)
{
StackPush(&st,right);
StackPush(&st,keyi+1);
}
if(left<keyi-1)
{
StackPush(&st,keyi-1);
StackPush(&st,left);
}
}
StackDestroy(&st);
}
用队列也能完成,用队列的话就是以层序遍历的方式进行,模拟不出函数调用堆栈的实际情况
只是访问的顺序不一样,要保持需要的那个顺序,那就要用栈
七、⚽总结
在八大排序算法中,快速排序是非常快的也是非常重要的,但是普通的快速排序还没有那么快,快的是优化过后的快排
加粗样式