这篇文章我会用自己的话从两个常见的排序算法:归并排序和快速排序入手,讲解分治思想在其中的运用,并且结合具体题目进行讲解其运用。
分治思想
它的核心思想是把复杂冗杂的问题进行一步一步地拆分,使其变成一个一个简单可处理的问题,最后再把小问题合并。在算法中它常常用递归来进行问题的拆解,解决,合并。大部分的分治可分为三步:
- 分成子问题
- 递归处理子问题
- 子问题合并
快速排序
快排思想图
首先先讲下快速排序中的思路也就是我们为什么会这样想,拿到数组之后我们的想法是用分治的思想,将数组不断拆成两个数组,保证这两个数组在之后的排序中不会再发生交集,最后拆解到只剩两个数时也就是最小子问题时停止拆解。
然后再讲一下具体的做法,由于我们的目标是让整个数组有序,所以可以选定一个数,使得左边的区间都是小于等于这个数的数,而右边则相反,然后再进行递归处理。这里的重难点也是如何使得左边区间和右边满足此条件。这里提供两个方法。
方法一 暴力
开辟两个数组分别存储两个区间的数,最后再赋值到原数组中即可
方法二 优雅
运用双指针的方法,左右各一个指针往中间走,左边的指针满足左边的条件时也就是小于选定的那个数时往中间走,右边同理,使得指针走过的地方都满足我们的要求,最后指针停止之后就是我们想要的结果了,这里的证明和边界判断的分析比如存在重复的数、一开始就选到最大或者最小等情况,感兴趣的可以移步AcWing 785. 快速排序算法的证明与边界分析 - AcWing 这里讲的很清楚。 最后贴一个y总的快排模板
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + 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);
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
运用:求第k大的数
方法一:我的暴力
首先当然是从暴力开始想起,就是一个一个数遍历,遇到一个大的数排名就加一,显然时间复杂度是O(n*n)显然会超时。
方法二:快排优化
快排已经很快了,我们当然可以排完之后直接读第k个小的数,但是还是存在优化空间的,思路还是从暴力开始一步一步想有哪里有冗余的地方可以优化的,由于我们只需要知道第k个数即可,并且分治时左右两边是存在一个大小关系的,所以我们对不包括第k小的数字的区间排序是多余的,只需要对包含的区间排序即可
归并排序
归并排序是一个比较典型的分治算法,和快排的思路一样都是想办法把它拆解成最小子问题解决,但是比快排多了一个区间合并的过程,它的前提是两个区间都已经是有序的状态了,再把两个区间有序合并会简单很多。 难点主要是在合并这个过程中,注意一下各种情况,如大于小于等于即可。
具体思路
- 确定划分边界
- 递归处理子问题
- 合并子问题:
- 主体合并
- 边界情况收尾
- 覆盖回来
运用:求逆序对总和
这道题我看的时候也是只会暴力做法,也就是遍历,看了题解之后发现有那么多解法,有些还没学到的以后再看好了😰 先讲运用归并排序的方法
首先:明确问题
好好看一遍题目,明白逆序对的定义
其次:分析、处理问题
处理问题的方法有很多,比如等价转换,分类处理,这里我们尝试分类。我们将序列从中间分开,把逆序对分成三类:
- 在左边区间的逆序对
- 在右边区间的逆序对
- 在两边区间的逆序对
这里有一个关键的地方需要注意到,对于第三种逆序对,我们调动两边的位置是不会影响它的数量的,所以我们可以用归并排序(我知道这里的思路非常跳跃我如果没看题解也想不到这里)这样两边的逆序对就非常好判断了,并且我们只需要遍历一下右边的数列中比左边小的部分,就可以知道第三种逆序对的数量了,但是我们还需要对两边进行排序所以要在归并排序的基础上添加一个比较之后右边的数小于左边的数时算出逆序对的过程即可。
总结
分治算法的核心还是拆解问题,感觉算法题大部分一开始做是做不出来的,有些思路是真的很难凭空想出来的,需要不断积累解法和思想,从做过的题目和解法中吸取经验,好好加油吧可能有时候不用花太大力气总结,多做多想可能效果更好