算法基础课学习笔记:(一)排序
记录一下算法学习过程,学习最佳的开始时间或是十年前或是现在。
本文全部算法学习均基于Acwing算法基础课程,有兴趣的可以购入视频课程学习
给y总打波广告hh。
本文仅介绍常用快速排序及归并排序,堆排序和希尔排序会放到后面讲。
一、快速排序Quick_sort():
快速排序基于Two Point思想,将时间复杂度从O(
n
2
n^2
n2)降到了(
n
l
o
g
n
nlogn
nlogn),适用于
1
0
5
10^5
105及以上的数据。
PS:若数据本身有序,会极大地影响快排的时间复杂度,导致效率降低,酌情考虑使用情景
1.算法思想
先来看一组数据
排序即是将一组无序的数据变为升序或降序,快速排序借助两个下标指针
l
e
f
t
left
left和
r
i
g
h
t
right
right,从前向后或从后向前遍历整个数组。
第一步:保证左右半段各自小于或大于主元
快速排序需要确定主元,此处我们以A[1]为例,将A[1]存进临时变量
t
e
m
p
temp
temp中,
l
e
f
t
left
left指向数据最左端A[1],
r
i
g
h
t
right
right指向数据最右端A[11]。
接下来是快速排序的核心思想:
1.只要 r i g h t right right指向的元素A[right]大于 t e m p temp temp,就将 r i g h t right right不断左移;当某个时刻A[right]<= t e m p temp temp时,将元素A[right]移到 l e f t left left指向的元素A[left]处。
2.只要 l e f t t leftt leftt指向的元素A[left]大于 t e m p temp temp,就将 l e f t left left不断左移;当某个时刻A[left]> t e m p temp temp时,将元素A[left]移到 r i g h t right right指向的元素A[right]处。
3.不断重复第二步和第三步,直到 l e f t left left和 r i g h t right right相遇,将 t e m p temp temp变量中存的数放入相遇的位置
我们可以发现,此时 l e f t 和 r i g h t left和right left和right指向的A[6]元素存储的就是原先A[1]的元素。
重要特征:此时A[1]到A[5]严格满足
<
=
<=
<=A[6],A[7]到A[11]严格满足
>
>
>A[6]
此时,我们的快速排序第一步就完成了,将A[1]作为中心元素放到A[6]的位置,满足了左半段小于原来A[1]的元素、右半段大于原来A[1]的元素。
第二步:拆解数组
我们将左半段和右半段拆成两个分离的数组记为
B
数
组
和
C
数
组
B数组和C数组
B数组和C数组,对
B
、
C
数
组
B、C数组
B、C数组分别进行快速排序,排序完后再将
B
、
C
数
组
B、C数组
B、C数组拆解后再分别排序,直到全部拆解小区间的元素长度为
1
1
1。
这听起来像什么??这不就是无限套娃吗??
说到套娃,我们想到的是什么??递归啊!!
对,将左右两半的数组无限递归下去,直到小区间元素长度为
1
1
1,就
r
e
t
u
r
n
return
return回上一级。下面给出快排的代码模板,借y总的话说,背就完事了。
void quick_sort(int q[],int left,int right){
if(left>=right) return;
int tmp=q[left+right>>1];//确定主元
int i=left-1,j=right+1;//划分区间
while(i<j){
do i++;while(q[i]<tmp);
do j--;while(q[j]>tmp);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,left,j);
quick_sort(q,j+1,right);
}
此时,我们就完成了快速排序,原数组排序完满足升序,如果想要降序,仅需在判断时将
<
和
>
<和>
<和>号互换即可。
PS:快速排序的时间复杂度并不稳定,算法本质也不稳定,但并不影响快排的优越地位。
注意!!选择主元的位置直接决定了排序效率,此处模板直接选择了最安妥的中位数。
二、归并排序merge_sort():
如果说快速排序是将数组分段后交换元素位置,再不断递归。那归并排序就是一上来将所有元素打散成长度为
n
n
n的小区间,再在合并区间时做手脚。(乱拳打死老师傅hh)
小区间长度
n
n
n为几,就被称为几路归并排序。
例如序列{66,12,33,57,64,27,18},要将该序列进行
2
2
2路归并排序。
1.两两分组,得到四组:{66,12},{33,57},{64,27},{18}组内单独排序,得到新序列{{12,66},{33,57},{27,64},{18}};
2.合并为两组,{12,66,33,57}和{27,64,18},组内单独排序得{12,33,57,66}和{18,27,64};
3.合并为1组,得到{12,66,33,57,27,64,18},组内单独排序得{12,18,27,33,57,64,66},完成排序
我在阅读到这一步的时候有所疑问,为什么要将一次排序就能得到的结果拆解为数个小排序呢?
其实是因为在合并区间的时候可以继承原先区间的单调增减性,只要在合并区间时动些手脚,就能减少重复计算。
我们可以发现,归并排序的重点就在于合并区间,如何合并区间才能减少重复计算?这里我们将再次借助Two Point思想。
下面给出归并排序的代码模板,背就完事了。
void merge_sort(int q[],int l,int r){
if(l>=r) return;
int mid=l+r>>1;
merge_sort(q,l,mid),merge_sort(q,mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if(q[i]<=q[j]) tmp[k++]=q[i++];
else tmp[k++] = q[j++];
}
while(i<=mid) tmp[k++] = q[i++];
while(j<=r) tmp[k++] =q[j++];
for(i=l,j=0;i<=r;j++,i++) q[i]=tmp[j];
}
此时,我们就完成了归并排序,归并排序的一大应用就是:可以在合并区间或将单独小区间排序的时候,统计一些数据量。
这里给出具体习题,可以考虑如何用归并排序完成
三、万金油—sort():
我们上面讲了这么多,其实在实际应用中最常使用到的应该就是这个了hh。
int q[5]={1,3,2,5,4};
sort(q,q+5);
考试的时候直接用!没人给你扣分hh。
本文借助Acwing算法基础课程及《算法笔记》完成。
文中图片来源于《算法笔记》。