又被分治题卡住好几个小时!用最笨的方法搞懂分治法边界,告别死循环!
这篇文章写于我刚学算法时。好家伙,第一道题快排就卡我老半天。但是好消息是,我算是没有得过且过,花了一晚上和一上午,把所有情况都捋了一遍、把迭代过程考虑清楚了。之后便感觉入了门,有了感觉,后续其他题目都没有卡我这么久过。
被很简单的快排 代码运行状态: Memory Limit Exceeded
老半天。
最后琢磨半天越界这事儿。总结起来一句话:避免出现 func(l, r) { ... func(l, r) ... }
(递归时传递到下一层的边界值不缩小)这种情况,因为这是死循环。 如何避免? 比如func(l, r) { func(l, j), func(j + 1, r)}
中,j
至少满足 j > r
(j
从r
身上离开,防止 func(l, j) 是 func(l, r)
),就可用。
#include <iostream>
using namespace std; const int N = 1e6 + 10; int n; int q[N];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);
}int main() { scanf("%d", &n); for (int i = 0; i < n; i ++) scanf("%d", &q[i]); quick_sort(q, 0, n-1); for (int i = 0; i < n; i ++) printf("%d ", q[i]); return 0; }
手贱,非得写成:
quick_sort(q, l, i - 1), quick_sort(q, i, r);
好家伙,报错。半天没看出来,后来才恍然大悟,你要是用 i
分界,上面得是 x = q[l + r + 1 >> 1];
。
那我下面这样不行吗?
x = q[l+r >> 1];
...
quick_sort(q, l, j - 1), quick_sort(q, j, r);// 或者这样不行吗?
x = q[l+r >> 1];
...
quick_sort(q, l, i - 1), quick_sort(q, i, r);// 或者这样不行吗?
x = q[l+r >> 1];
...
quick_sort(q, l, i), quick_sort(q, i + 1, r);// 或者这样不行吗?
x = q[l+r+1 >> 1];
...
quick_sort(q, l, j), quick_sort(q, j + 1, r);// 或者这样不行吗?
x = q[l+r+1 >> 1];
...
quick_sort(q, l, j - 1), quick_sort(q, j, r);// 或者这样不行吗?
x = q[l+r+1 >> 1];
...
quick_sort(q, l, i), quick_sort(q, i + 1, r);
上述都不行,看我一一举反例。
我们输入长度是2
的数组,则第一层循环:l = 0, r = 1
(即 quick_sort(0, 1)
),如果进入第二层循环时,还出现 quick_sort(0, 1)
的情况,则陷入死循环。
下表中,“传到函数的
i, j
”指调用quick_sort(q, l, ?i/j), quick_sort(q, ?i/j, r)
中i, j
的值。
下表中,最后一列标记
x
表示将使程序陷入死循环。
对于 int mid = l+r >> 1;
:
测试用例 |
q[mid]
|
传到函数的i, j
|
传入参数 |
---|---|---|---|
0 1
|
0 |
0, 0
|
j-1 j => (0, -1), (0, 1)x
|
0 1
|
0 |
0, 0
|
i-1 i => (0, -1), (0, 1)x
|
0 1
|
0 |
0, 0
|
j j+1 => (0, 0), (1, 1) √
|
1 0
|
1 |
1, 0
|
i i+1 => (0, 1)x, (2, 1)
|
1 0
|
1 |
1, 0
|
j j+1 => (0, 0), (1, 1) √
|
可见,在 int mid = l+r >> 1;
时,四种组合中只有 j j+1
经受住了 0 1
和 1 0
的双重考验。
对于 int mid = l+r+1 >> 1;
:
测试用例 |
q[mid]
|
传到函数的i, j
|
传入参数 |
---|---|---|---|
1 0
|
0 |
1, 0
|
j-1 j => (0, -1), (0, 1)x
|
1 0
|
0 |
1, 0
|
i i+1 => (0, 1)x, (2, 1)
|
1 0
|
0 |
1, 0
|
i-1 i => (0, 0), (1, 1) √
|
0 1
|
1 |
1, 1
|
j j+1 => (0, 1)x, (2, 1)
|
0 1
|
1 |
1, 1
|
i-1 i => (0, 0), (1, 1) √
|
可见,在 int mid = l+r+1 >> 1;
时,四种组合中只有 i-1 i
经受住了 0 1
和 1 0
的双重考验。
这是为什么呢?
- 这里有相关证明:AcWing 785. 快速排序算法的证明与边界分析
- 如果你没耐心看上述较为严谨的证明,可以看文末我写的
我用比较笨的方法理解是:
int mid = l+r >> 1;
:则可证明j
的取值范围是[l, r-1]
,因此对于边界组合j j+1
有quick_sort(q, l, j小于r), quick_sort(q, j+1大于l, r)
,永远都不会有quick_sort(q, l, r)
出现。int mid = l+r+1 >> 1;
:则可证明i
的取值范围是[l+1, r]
,因此对于边界组合i-1 i
有quick_sort(q, l, i-1小于r), quick_sort(q, i大于l, r)
,永远都不会有quick_sort(q, l, r)
出现。
OK,那下面就是背诵:
- 快排中,
int mid = l+r >> 1;
(mid
向下取整),是j j+1
,因为j
取值范围是[l r-1]
- 我个人是不太喜欢背诵的,还是知道原理,觉得到时候可以快速推导出来靠谱,推导如下。
用较清晰但是笨拙的方法证明一下 mid
向下取整时 j
属于 [l, r-1]
。
向下取整时 j
属于 [l, r-1]
等价于 向下取整时至少有两次 j--
被执行
下面分三种特殊情况讨论(普通情况不讨论),可以看出三种情况中都至少有两次 j--
被执行
情况1:j
在r
处就不再q[j] > x
,而i
在l
处还满足q[i] < x
q[mid] x9 8
begin i j
step1 i j do i++; while(q[i] < x);
step2 i j do j--; while(q[j] > x);
step3 8 9
step4 i j swap(q[i], q[j]);
step5 ij do i++; while(q[i] < x);
step6 j i do j--; while(q[j] > x);
跳出循环 while(i < j) {}
j
在r
处就不再q[j] > x
,而i
在l
处还满足q[i] < x
;因此对于l < r
,还要再跳一轮,因为是 do while
而不是 while do
,所以不管 i
或 j
什么条件,都得再至少来一次 i++; j--;
。
情况2:j
在r
处还满足q[j] > x
,而i
在l
处就不再q[i] < x
q[mid] x8 9
begin i j
step1 i j do i++; while(q[i] < x);
step2 ij do j--; while(q[j] > x);
step3 8 9
跳出循环 while(i < j) {}
j
在r
处还满足q[j] > x
,因此,一定会继续执行j--
,j
一定会小于r
。
情况3:j
在r
处就不再q[j] > x
,且i
在l
处就不再q[i] < x
q[mid] x8 8
begin i j
step1 i j do i++; while(q[i] < x);
step2 i j do j--; while(q[j] > x);
step3 8 8
step4 i j swap(q[i], q[j]);
step5 ij do i++; while(q[i] < x);
step6 j i do j--; while(q[j] > x);
跳出循环 while(i < j) {}
j
在r
处就不再q[j] > x
,且i
在l
处就不再q[i] < x
;此时有 i < j
,因此不跳出循环,执行 swap
;对于l < r
,还要再跳一轮,因为是 do while
而不是 while do
,所以不管 i
或 j
什么条件,都得再至少来一次 i++; j--;
。
这里的魅力在于 do while
:不管咋的,你满不满足条件,我先给你移动一下,你再判断。
对于二分法,核心思想也是避免出现func(l, r) { func(l, r); }
,因此出现 mid = l + r >> 1;
则必有 r = mid;
,因为 mid
是向下取整,l < r
时 mid
肯定碰不到 r
。
我是小拍,记得关注给个在看!
又被分治题卡住好几个小时!用最笨的方法搞懂分治法边界,告别死循环!相关推荐
- python矩阵乘法分治算法_矩阵乘法的Strassen算法详解 --(算法导论分治法求矩阵)...
1 题目描述 2 思路分析 3 解法 4 小结 1 题目描述 请编程实现矩阵乘法,并考虑当矩阵规模较大时的优化方法. 2 思路分析 根据wikipedia上的介绍:两个矩阵的乘法仅当第一个矩阵B的列数 ...
- python分治算法_python算法实现-分治法
分治法概念将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题----"分" 将最后子问题可以简单的直接求解----"治" 将所有子问 ...
- 成功解决ValueError: array must not contain infs or NaNs(花了好几个小时解决了这个最离奇的bug)
成功解决ValueError: array must not contain infs or NaNs(花了好几个小时解决了这个最离奇的bug) 目录 解决问题 解决思路 解决方法 问题背景
- 点分治题单(来自XZY)
点分治题单(来自XZY) 静态点分治 [x] 洛谷 P3806 [模板]点分治1 [x] 洛谷 P4178 Tree [x] 洛谷 P2634 [国家集训队]聪聪可可 [x] 洛谷 P4149 [IO ...
- NOI20102010年,世博会在中国上海举办,吸引了数以千万计的中外游客前来参观。暑假期间小Z也来到了上海世博园, 她对世博园的拥挤早有所闻,对有的展馆甚至要排上好几个小时的队才能进入也做好了充分
NOI2010 2010年,世博会在中国上海举办,吸引了数以千万计的中外游客前来参观.暑假期间小Z也来到了上海世博园, 她对世博园的拥挤早有所闻,对有的展馆甚至要排上好几个小时的队才能进入也做好了充分 ...
- 分治法的关键特征_经典算法思想2——分治(Divide-and-Conquer)
分治法,字面意思是"分而治之",就是把一个复杂的1问题分成两个或多个相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接求解,原问题的解即子问题的解的合并,这 ...
- 题223.2022寒假天梯赛训练-7-12 清点代码库 (25 分)
文章目录 题223.2022寒假天梯赛训练-7-12 清点代码库 (25 分) 一.题目 二.题解 题223.2022寒假天梯赛训练-7-12 清点代码库 (25 分) 一.题目 二.题解 我这个做法 ...
- 2_2 递归与分治策略(分治法的基本思想)
基本思想:将复杂问题简单化,大事化小,小事化了,很符合中国人的思想. 打官腔:分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同.求出子问题的解, ...
- 四种解法——求子序列的最大连续子序和(普通解法、求和解法、分治法、O(n)级解法)(面试经典题)
励志用少的代码做高效表达 在这四种解法里,解法一是通法,可以学到规律和知识,做基础之用:解法二在解法一的基础上做改进,锻炼思维:解法三则是大名鼎鼎的分治法,涉及到递归的知识,算是"高效算法设 ...
最新文章
- 无人驾驶汽车系统入门——基于Frenet优化轨迹的无人车动作规划方法
- c语言贪心算法合并箭,LeetCode刷题题库:贪心算法
- bzoj1935: [Shoi2007]Tree 园丁的烦恼
- LeetCode 257. 二叉树的所有路径 思考分析
- stm32 adc 连续和扫描_技术分享 | STM32多个ADC模块同时采样转换的应用示例
- Shell——运行Shell脚本
- 复数基础——虚数和复数_5
- Wi-Fi 爆重大安全漏洞,Android、iOS、Windows 等所有无线设备都不安全了
- C++类复制构造函数
- 9种退出极域课堂的方法
- Proteus 8.6 SP2 Pro 汉化破解版(附破解文件+汉化包+安装教程) 兼容win10
- MySQL逻辑架构及工作流程
- Python调用摄像头
- html+css+js制作点名器
- Springboot微信点餐系统——01项目设计以及初步环境搭建
- Flyme patchrom项目笔记
- 网络信息化时代,3D产品建模宣传很到位
- 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?
- 一看就懂的vue简版源码概述
- 图结构解决农夫过河问题(二附源代码)
热门文章
- Postgre 中的空值判断
- 大规模数据运行时,可以考虑使用多线程处理!
- 如何防止盗号 使用windows自带的 屏幕键盘 OSK
- fftw3 嵌入式linux安装,Ubuntu18.04下快速的安装UHD与GnuRadio并连接USRP设备
- 3DSMAX安装未完成,某些产品无法安装的解决方法
- 关于C#关闭窗体后,依旧有后台进程在运行的解决方法
- 中心/设置地图缩放以覆盖所有可见的标记?
- linux tomcat连接mysql步骤_Linux安装JDK 、TOMCAT 、MYSQL 步骤
- pdf模板工具JaspersoftStudio,JasperReport
- form表单提交数据的同时上传文件代码示例