目录
一、前言:
Hai~,大家周末晚上好!我是夏日弥,很高兴看见您来读我的博客;
本周将为您带来一些算法基础讲解,从经典的快速排序开始说起吧,我们将依次介绍快排的算法思想和图解,并附上代码模板和相关注释,以及一些网站上的例题;
让我们开始本周的算法之旅吧!
二、何为快速排序?
2.1、思想
快速排序的思想是分而治之(Divide and conquer)——把一个大的问题划分成一个个小问题,再分别处理几个小问题,从而解决大问题。
我们对一个完全无序的数组做如下操作:
1、选取一个随机值x,然后设立两根指针 i 和 j 分别指向数组的头部和尾部;
2、 i 必须指向一个大于 x 的数,否则它将向中间前进一位;
j 必须指向一个小于 x 的数,否则它将向中间前进一位;
当 i 和 j 都停下来时,若 i < j 时,交换彼此的值;
3、完成交换后,强制 i 和 j 往中间移动一位;
4、当 i 越过了 j 或者 i 和 j 重叠后停止;此时确保这是一个局部有序的数组:
j 的右边的数全部小于 x ;
i 的左边的数全部大于 x ;
5、分别对 i 的左边区域 和 j 的右边区域重复递归 1,2,3,4的步骤;直至整个数列有序;
2.2、图解:
我们将进行1,2,3,4的步骤;
我们来看第一个图解:
然后我们将在期中调一个随机的值作为"分界数",我们叫它x,小夏喜欢取中间的值,所以小夏取3;
我们将设立两根指针分别从数组头部和尾部扫描;
指针i的使命是指向一个大于x的数字,如果它没有指向这个数字,就让它往右边移动一位;
指针j的使命是指向一个小于x的数字,如果它没有指向这个数字,就让它往左边移动一位;
当两根指针都停下来的时候,如果i<j,即左边的指针没有越过右边的指针,也没有重合的话,就交换它们的位置;
如图;
您看, i 与 j 只是各取所需而已,i 要确保它的左边所有的元素都小于或者等于x ,
j 要确保它右边的所有元素都大于或者等于x;
当它们指向的元素都不符合自己的需求时,它们就会交换彼此的值;
交换过后,i 与 j 会分别往中间前进一步 ,以免出现以下的死循环;
好了,交换过后必须强制 i 和 j 往中间前进一步!这很重要!
好了,让我们回到刚刚的样例:
再一次扫描,执行;
最后 i 和 j 都指向了3,也就是我们所说的中间值;
您看,i 的左边全部是小于或者等于3的元素对吧!
j 的右边全部是大于或者等于3的元素对吧!
至此,我们完成了第一趟排序;接下来,我们将对 i 左边的区域再次执行一次相同操作的排序;
第二趟排序:
起始位置:
本次取的中间值是 x = 2;
j 没有指向一个小于 x 的元素,所以把它往前移动;
完成交换后,我们强制 i 和 j 各自往中间移动一位;
如下:
至此,完成第二趟排序;
再次截取前面的(0,j)区间排序,以此类推,最后您会得到只剩一个数的序列{1},只有一个数的序列当然是有序的;
这个部分也被称为“递归调用”,我们将分别递归(0,j),( j , r )的区域,直至整个数列变得有序而完整;
快排的时间复杂度为 N⦁logN;
因此
2.3、模板
接下来小夏会给您分享一个模板,小夏很喜欢整个模板,因为它足够地“简洁和优雅”;
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;//定义常量N,小夏开的是100010,您可以按照需求随时修改;
int n;
int a[N];
void quick_sort(int a[],int l,int r){//参数l是数组开始排序的位置,参数r是排序结束的位置;
if(l >= r)return ;//如果数组是空的或者它只有一个元素,那么它自然是有序的,返回;
int x=a[(l+r)/2];//取一个随机值x,小夏取的是中间值;您可以取头部值或者尾部值都是没问题的;
int i = l-1,j = r + 1;/*细节问题,小夏采取了强制移动以防止死循环,
所以在定义i和j时将它们分别往前边和后边移动一位
*/
while(i<j)//左针小于右针时进行;
{
do i++;while(a[i] < x);//先强制移动一位,再判断i指向的值是不是小于x,是的话往前移动;
do j--;while(a[j] > x);//先强制移动一位,再判断j指向的值是不是大于x,是的话往前移动;
if( i < j){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}//当i和j都停下来,i在j左边时,交换它们指向的值
}
quick_sort(a,l,j);//递归调用,以对前面排序;
quick_sort(a,j+1,r);//递归调用,以对后面进行排序;
}
int main(){
scanf("%d",&n);
for(int i= 0 ;i<n;i++)scanf("%d",&a[i]);
quick_sort(a,0,n-1);
for(int i = 0;i<n;i++)printf("%d ",a[i]);
return 0;
}
相关的注释已经为您写好;
三、例题:
3.1、洛谷-P1177-快速排序;
唔...小夏第一次写这道题也很头疼,因为总是爆时;
小夏猜了几个原因,可能是数组开得不够大,可能是随机值取得不太好;
为您附上题目:
题目描述
利用快速排序算法将读入的 N\N 个数从小到大排序后输出。
快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++ 选手请不要试图使用 STL
,虽然你可以使用 sort
一遍过,但是你并没有掌握快速排序算法的精髓。)
输入格式
第 11 行为一个正整数 NN,第 22 行包含 NN 个空格隔开的正整数 a_iai,为你需要进行排序的数,数据保证了 A_iAi 不超过 10^9109。
输出格式
将给定的 NN 个数从小到大输出,数之间空格隔开,行末换行且无空格。
输入输出样例
输入 #1
5 4 2 4 5 1
输出 #1
1 2 4 4 5
3.1.1、题解
#include<bits/stdc++.h>
using namespace std;
const int N = 1000100;//定义常量N,小夏开的是1000100,您可以按照需求随时修改;
int n;
int a[N];
void quick_sort(int a[],int l,int r){
if(l >= r)return ;//如果数组是空的或者它只有一个元素,那么它自然是有序的,返回;
int x=a[(l+r)/2];//取一个随机值x,小夏取的是中间值;您可以取头部值或者尾部值都是没问题的;
int i = l-1,j = r + 1;/*细节问题,小夏采取了强制移动以防止死循环,
所以在定义i和j时将它们分别往前边和后边移动一位
*/
while(i<j)//左针小于右针时进行;
{
do i++;while(a[i] < x);//先强制移动一位,再判断i指向的值是不是小于x,是的话往前移动;
do j--;while(a[j] > x);//先强制移动一位,再判断j指向的值是不是大于x,是的话往前移动;
if( i < j){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}//当i和j都停下来,i在j左边时,交换它们指向的值
}
quick_sort(a,l,j);//递归调用,以对前面排序;
quick_sort(a,j+1,r);//递归调用,以对后面进行排序;
}
int main(){
scanf("%d",&n);
for(int i= 0 ;i<n;i++)scanf("%d",&a[i]);
quick_sort(a,0,n-1);
for(int i = 0;i<n;i++)printf("%d ",a[i]);
return 0;
}
(没错啦小夏就是大懒鬼,就是上边的代码,只不过数组开大了一点);
四、尾声
小夏最近处于严重的怠倦期QAQ,
还在努力支棱起来;
已经把大部分的金钱和时间投入可爱键盘上了怎么办啊
特别鸣谢洛谷提供的例题;
感谢您的阅读;
我们下周再见,掰掰~
夏日弥死傲娇
2021.11.28;