快速排序
快速排序的核心思想是分治,其算法大体可分为三个部分:
- 确定一个分界点
pivot
(这个分界点可以任取,一般来说取左端点或中间端点); - 调整区间,将小于
pivot
的数和大于pivot
的数分开; - 递归处小于
pivot
和大于pivot
的部分。
这里我们用到了双指针,即在待排序数组的两端定义两个指针 i
和 j
,i
不断向右移动直到 q[i] > pivot
,j
不断向左移动直到 q[j] < pivot
,这时将 q[i]
和 q[j]
交换,之后递归进行处理。
这样处理的好处就是 i
左边的数永远是小于 pivot
, j
右边的数永远是大于 pivot
。
下面给出快速排序的C++模板:
void quick_sort(int q[], int l, int r){
if(l >= r) return;
int x = q[(l + r) / 2], i = l - 1, j = r + 1;
while(i < j){
do i++; while(q[i] < x);
do j--; while(q[j] > x);
if(i < j){
swap(q[i], q[j]);
}
}
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
(在使用此模板时轻易不要改动,否则容易出现边界问题。。。。)
归并排序
归并排序的核心思想也是分治,其算法大体也可以分为三个部分:
- 确定分界点(这个分界点是数组的中点,不能改变);
- 递归排序左半部分和右半部分;
- 归并–将两部分数组合二为一。
发现与快速排序不同的地方了吗?
快速排序是先处理再递归,不需要使用额外的空间,而归并排序是先递归再处理,而且需要使用额外的空间。
此算法的核心就是如何将两排序数组合二为一,这里我们依然采用双指针法。我们开辟一个额外的数组空间 tmp
,定义两个指针分别指向已经分割好的两数组的第一个元素,然后对指针所指向的元素的值进行比较,取较小者加入新数组 tmp
,当其中一个数组全部加入后,将另一个数组按顺序加入 tmp
即可。
下面给出归并排序的C++模板:
void merge_sort(int q[], int l, int r){
if(l >= r) return;
int mid = l + (r - l) / 2;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
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(int i = l, j = 0; i <= r; ++i, ++j){
q[i] = tmp[j];
}
}
快速排序和归并排序的核心思想都是分治,从另一种层面上理解,快速排序实质上就是一个二叉树的前序遍历,读到这里相信很多小伙伴是理解不了的,我们可以这样理解:快排的步骤是先构造一个分界点,然后再递归的处理左右子数组,这不就是二叉树的前序遍历(根左右)吗?同理,我们可以得出归并排序实质上就是一个二叉树的后序遍历。
这两个算法还是比较重要的,建议在理解的基础上背一手模板就很舒服。