蛮力法的主要思想就是用最简单的思路解决问题,一般性能不好,但仍然很重要。
- 理论上蛮力法可以解决可计算领域的各种问题
- 蛮力法解决较小规模问题是可接受的,如果设计一个更高效算法代价不值得
- 蛮力法可以作为时间性能的底线,来衡量更高效的算法
查找问题中的蛮力法
顺序查找
按照顺序进行查找,一般可以对算法进行适当优化,如设置哨兵等,能够改进时间性能,但只能减少系数,而数量级不会改变。
- 两数之和 - 简单 代码
- 剑指 Offer 53 - II. 0~n-1中缺失的数字 - 简单 代码
串匹配问题
串匹配定义:给定两个串S=”s1s2s……n“和T=”t1t2t……m“,在主串S中查找子串T的过程称为串匹配(也称模式匹配),T称为模式
BF算法
绝对的蛮力:从主串S的第一个字符开始和模式T的第一个字符进行比较,若相等,则继续比较二者的后续字符;若不相等,则从主串S的第二个字符开始和模式T的第一个字符进行比较,重复上述过程,若T中的字符串全部比较完毕,则说明本趟匹配成功;若最后一轮匹配的其实位置是n-m,则主串S中剩下的字符不足够匹配整个模式T,匹配失败。
KMP算法
基本思想是:主串不进行回溯
画了一个简图,对于主串S和子串T,在叉号位置(即j)不一致了,此时T的子串1(abcd)和子串2(abcd)内容是一致的,将子串1的下一位(即星号所在位置k)移到叉号所在位置,就可以继续进行比较。这个思路是不是就可以快速提高匹配效率。
所以这个算法需要先计算出不匹配字符前面的字符串,即是真前缀又是真后缀的最长子串。将这个子串长度加一便是当前字符不匹配时,需要右移的到T的字符的位置。我们将这个值记录到next数组中。
next[j]的值意味着当j不匹配的时候,从T的第k个字符进行比较。
计算next[j]的方法:假设next[j]的值是k
- 如果t[k]=t[j],那么next[j+1]=k+1
- 如果t[k]≠t[j],则需要查看t[next[k]]是否等于t[j],如果相等,则next[j+1]=next[k]+1,否则继续往前追溯。这么做的原因是next里是算的真前缀和真后缀,所以t[j]必然是要参加比较的,而真前缀已经在前面的字符串里计算好了,直接使用即可。
所以最终算法思路为,先计算T的next,然后将S和T进行比较,当在T的位置j不一致的时候,将T滑到next[j]的位置,继续比较。
- 686. 重复叠加字符串匹配 - 简单 代码
排序问题中的蛮力法
选择排序
扫描整个序列,找到整个序列的最小记录和序列中的第一个记录交换,从而将最小记录放到它在有序区的最终位置上,然后再从第二个记录开始扫描序列,重复这个过程。
起泡排序
起泡排序开始的时候扫描整个序列,扎起扫描过程中两两比较相邻记录,如果反序则交换,最终,最大记录就被沉到了序列的最后一个位置,第二趟扫描将第二大记录沉到了导数第二个位置,重复操作,知道n-1趟扫描后,整个序列就排序好了。
起泡排序有优化方案
- 如果没有交换,说明排序已经完成,无需继续排序
- 如果在某个位置之后,再也没有交换,说明在该位置之后都是排序好的,该位置之后无需继续排序
- 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 - 中等 代码 代码正确,但是超时了,这个算法就是慢
组合问题中的蛮力法
生成排列对象
蛮力法生成{1,2,……,n}的n!排列。假设已经生成了(n-1)!个排列,可以把n插入到n-1个元素的每一种排列中的n个位置中去,来得到问题规模为n的所有排列。
生成子集
n个元素的集合A={a1,a2,……,an}的所有2^n的自己和长度为n的所有2^n的比特串之间的一一对应关系。
核心就在于这个一一对应关系,比特串里的0与1代表集合中的单个元素存在与不存在。
0/1背包问题
0/1背包问题是给定n个重量为{w1,w2,……,wn}、价值{v1,v2,……,vn}的物品和一个容量为C的背包,求这些物品中的一个最有价值的子集,并且要能够装到背包中。
这个问题使用蛮力法的话,就是计算出所有物品的子集,一个一个计算,看哪个价值最高。需要用到上面生成子集的方案。
这个就不做题了,一是因为主要功能在生成子集里已经做了,二是在乐扣里没有找到合适的题目,大家如果知道合适的题目可以推荐一下。
任务分配问题
假设有n个人物需要分配给n个人执行,每个任务只分配给一个人,每个人只分配一个任务,且第j个任务分配给第i个人的成本是C[i,j](1<=i,j<=n),任务分配问题要求找出总成本最小的分配方案。
这个问题使用蛮力法的话,就是生成所有任务的全排列,总数为n!,然后一个一个计算哪个价值最高,需要用到上面生成排列对象的方案。
这个同样也不做题了,原因和0/1背包问题一样,核心逻辑在生成排列对象里完成了。总体而言,蛮力法解决组合问题,除非问题规模非常小,否则蛮力法几乎不实用。
图问题中的蛮力法
哈密顿回路问题
注明爱尔兰数学家哈密顿提出注明的周游世界问题,他用正十二面体的20个订单代表20个城市,要求从一个城市出发,经过每个城市恰好一次,然后回到出发城市。
用蛮力法解哈密顿回路问题
- 计算所有顶点的全排列,共有n!个
每个排列需要满足两个条件
- 相邻顶点有存在边
- 第一个顶点和最后一个顶点存在边
这个不做题了,核心还是求全排列,然后判断是否有边。真用蛮力法做,想想肯定得超时。
- 面试题 04.01. 节点间通路 - 中等
TSP问题
TSP问题是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,要求所走的路程最短。
这个问题和哈密顿回路的区别是加了路程最短的要求。
用蛮力法解TSP问题
- 计算所有顶点的全排列,共有(n-1)!个,这些排列中有一半是路径完全相反,所以可能的解缩减到(n-1)!/2个
- 遍历这些排列,并计算路程,选择路程最短的即可
这个也不做了,核心还是求全排列,必然超时
- LCP 16. 游乐园的游览计划 - 困难
几何问题中的蛮力法
最近对问题
最近对问题要求找出一个包含n个点的几何中距离最近的两个点。
蛮力法求解最近对问题的过程是:分别计算每一对点之间的距离,然后找出距离最小的那一对,为了避免对同一对点计算两次距离,只考虑i<j的哪些点对(Pi,Pj)。
- 1512. 好数对的数目 - 简单 代码
凸包问题
凸集合:对于平面上的一个点的有限几何,如果以集合中任意两点P和Q为端点的线段上的点都属于该集合,则称该集合为凸集合。
凸包:一个点集S的凸包是包含S的最小凸集合,其中,最小是指S的凸包一定是所有包含S的凸集合的子集。
凸包人话版:凸包就是你画了一个封闭的范围,这个范围包含了集合中所有的点。最小凸包就是利用集合S上的点构建封闭范围,里面包含集合所有点
使用蛮力法计算凸包:对于一个由n个点构成的集合S中的两个点Pi和Pj,当且仅当该集合中的其他点都位于穿过这两点的直线的同一边时(假设不存在三点同线的情况),他们的连线是该集合凸包边界的一部分。对每一对点都检验一遍后,满足条件的线段构成了该凸包的边界。这套方案时间复杂度O(n^3)。
这个就不做了,做法和最近对问题差不多,主要区别是找到对后,需要和剩余的其他点再做一次运算。而且乐扣里没有发现相关的题目。
总结
看了这么多类型的题后,感觉蛮力法对有些问题还是有用的,对有些问题规模较小也还是有些用处的,对规模较大的问题,就完全没有办法了。
这里面的类型比较重要的有
查找问题:顺序查找,KMP
排序问题:选择排序、起泡排序。这两个方法效率都不好,今后可以使用更高效的排序算法。但这两种方法也提供了很不错的思想。
组合问题:生成排序对象、生成子集。这两个方法一个时间复杂度O(n!),一个为O(2^n),虽然效率不行,但是这是很多其他问题的基础,如图问题、0/1背包问题,任务分配问题等
几何问题:最近对问题、凸包问题,整体思路比较简单,两重循环可以解决。
这些里面,尤其是KMP、生成排序对象、生成子集,有时间可以自己动手写一下,还是有一定难度的。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
往期文章回顾:
算法
技术
- 浅谈微服务
- TCP性能优化
- 限流实现1
- Redis实现分布式锁
- Golang源码BUG追查
- 事务原子性、一致性、持久性的实现原理
- CDN请求过程详解
- 记博客服务被压垮的历程
- 常用缓存技巧
- 如何高效对接第三方支付
- Gin框架简洁版
- InnoDB锁与事务简析
读书笔记
思考