算法之道

算法之道

算法思想之我见

算法,作为一种解决问题的方案,属于智力活动;既然是活动,就有一定的基本指导原则;就像认识世界和改造世界的关系,认识世界可以更好地改造世界;在改造世界的过程中,我们又会重新认识世界,从而为以后改造世界提供帮助;

既然是抽象的思想,就应该和具体的方法、工具加以区别,或者说,应该注意到这种区别;关于这种区别,我觉得用《我的架构思想》周爱民 提到的认知的四个层次来理解是最合适不过的:

L0:实践经验;未经审视和加工的实践经验,包含真知灼见,但也包含大量的矛盾、混乱、随意和武断,各种成分纠缠在一起,往往将有效的实践变成一种不可重复的艺术行为;比如管理艺术、领导艺术、成功学、心灵鸡汤、诸子格言(这里,私以为管理、领导等虽然多属于实践经验,但还是有章可循的,接收批评);

L1:科学方法;一套具体的、一致的、系统的概念、方法和过程,重复这个过程可以产生局部或者全局最优结果;比如各领域内经过实践检验的框架、方法、流程、规则;

L2:方法论;能够指导我们产生科学方法的方法和思维模式;系统论和系统科学;

L3:认识论;从哲学高度上分析我们在所面对的领域之内是否能够以及为什么可以产生科学的认知,我们认知的边界在哪里;

孟岩(智百科技(AI100)合伙人)在为其写的序中提到,“设计模式可以被认为是系统的局部架构。”,算是L1层次的书籍了;“但是《设计模式》出版后,很遗憾的,整个社区将注意力转向罗列设计模式套路并且期望在实践中套用之。这应该说背离了设计模式提出的初衷,也导致设计模式盛极而衰。这是只关注 L1 而 L2 缺位导致的典型后果。”

在我看来算法思想应该属于L2层次的内容,它指导我们产生解决具体问题的科学方法和思维模式;也就是L2的指导思想将产生L1的科学方法;

如此,我认为满足L2要求特点的算法思想有:贪心、分治、动态规划、穷举;而像递归、递推、回溯等并不能指导我们产生解决问题的科学方法,它们本身就是一种科学方法,所以它们在认知层次中属于L1;

纠结于此,有人可能觉得不太明智,或者没有十分的必要,其实,这种看法也是对的;学习的终极目的是掌握,掌握的基本要求是理解;只要理解就好,只不过当我用搜索引擎搜索“算法思想”时,看得到“八大算法思想”、“五大算法思想”等文章,它们带给我很多启示,但是很乱;我需要看到它们之间的清晰关系,因为单纯的记忆毫无用处;在回家的路上,阅读了周爱民老师的《我的架构思想》 ,很受启发;于是再次翻出《算法的乐趣》这一本书,试图梳理出一个自己满意的理解方式;

算法之道

贪心

概念

“目光短浅”的贪心算法,是寻找最优解问题的常见模式;该模式将求解过程分为若干步骤,每一步骤都选取当前状态下最好的或最优的选择,也就是总是做出局部最有利的选择,并希望最后得到的结果也是最好的或者最优的;

贪心法每次决策都是以当前情况为基础并根据某个最优原则进行选择,这样就不考虑其他各种可能的情况;不考虑其他情况的代价就是,每一次都选最好的,整体的结果却不一定是最好的;

贪心原则在各种算法里都有体现(可能不是算法的主体),但是针对某些特定的问题,贪心法也是非常有效的;这里的“特定”问题具备的特征便是:如果每一步决策最优,那么最后结果就一定是最优的;

贪心法和分治法一样,都需要对问题分解,从而得到子问题;但是贪心法的独特之处在于,一旦子问题得到局部最优解,就不再改变,直到算法结束,也就是没有回溯的处理,也正因为这样,贪心往往得不到真正的最优解

甚至,大多数情况下,因为“目光短浅”,贪心会错过最优解,但是它简单、高效,就算得不到真正的最优解,也能得到近似最优解,通常作为其他算法的辅助算法使用;

应用步骤
  1. 建立对问题精准描述的数学模型,包括定义最优解的模型;(问题是什么?怎样的解才是最优的?)
  2. 将问题分解为一系列子问题,同时定义子问题的最优解结构;(子问题是什么?如何得到子问题的最优解?)
  3. 应用贪心原则确定每个子问题的最优解,并根据最优解模型使用局部最优解得到全局最优解;
举例
分硬币问题

25分、10分、5分和1分四种不限量硬币;需要给客人找41分硬币,要求数量最少,如何分配?

  1. 建立数学模型,定义最优解:4种硬币面值大于41的前提下,使用硬币数量越少,越“优”;
  2. 子问题:所需面值改变后,继续选择硬币,种类不变;
  3. 应用贪心原则(单个硬币面值越大,则达到相同面值使用的硬币数越少,因为1个即可):首先选择25*1,面值变为16;选择10*1,面值变为6;选择5*1,面值变为1;选择1*1,面值为0;一共使用4枚硬币。

该问题,使用贪心法的确得到了最优解;

硬币种类修改为:25分、20分、5分和1分四种不限量,同样给客人41分硬币,要求数量最少。此时如果按照贪心原则:25*1;5*3;1*1;共5枚。然而,最优解确是20*2;1*1;需要3枚;

面值达到25,的确是前者最优,符合贪心原则;但是面值达到40,前者便不是最优了。

0-1背包问题

N件物品+承重为C的背包,物品i的重量为wi,价值为pi;求解在不超重的情况下,如何选择物品使得总价值最高?

  1. 物品重量之和不超过C,总价值越高,越“优”;
  2. 装入某件物品后,容量减少,价值增加,继续选择装入的物品;
  3. 这里贪心原则就不唯一了,既可以选择价值高的物品先装入,也可以选择体积小的物品先装入,还可以选择单位体积价值高的物品先装入;

不同的贪心原则,最后得到的结果一般不同(特殊数据时相同);

当然,有一些著名算法就采用了贪心策略;比如最小生成树的Prim算法和Kruskal算法;另外,在Dijkstra的单源最短路径算法中也使用贪心策略作为辅助方法;

分治

概念

“分而治之”的分治算法,也是一种解决问题的常用思想;该模式将无法着手解决的大问题分解成一系列规模较小的相同类型问题,然后逐个解决小问题,即分而治之;

需要注意的是,分治法产生的子问题与原始问题类型相同,只是规模减小,反复使用分治法,使得面对的问题规模不断减小,直到可以被直接求解;

应用分治法一般有两个目的

  1. 通过分解问题,使无法着手解决的大问题变成容易解决的一系列小问题;
  2. 通过分解问题,使问题的规模减小,降低解决问题的复杂度;

事实上,上面两个目的存在一定的交叉,不过核心思想还是化难为易、分而治之;

应用步骤
  1. 分解:将问题分解为若干个规模较小、类型相同的子问题;
  2. 解决:如果子问题可以直接解决,那么就解决子问题;如果子问题不能直接解决,就再次使用分治法,该过程是一个递归过程;
  3. 合并:将获得的各个子问题的解通过某种规则合并起来,得到原问题的解;

第二步一般可以采用递归的方式完成,当然有些问题也可以采用非递归方式完成;

分治法的关键是分解策略的选择,分解的目的就是将大问题化为规模较小、类型相同的小问题;

举例
快速排序
  1. 分解:选择一个标兵数,将待排序元素分为两个子序列,其中一个子序列里所有元素都大于标兵数,另一个子序列里所有的元素都小于等于标兵数;
  2. 解决:对子序列应用快速排序;直到子序列的容量为1,即可以直接返回,排序结束;
  3. 合并:将子序列拼接在标兵数的前后,完成合并;
快速傅里叶变换
  1. 分解:将N点的离散傅里叶变换按照奇偶关系分成两个N/2点的子离散傅里叶变换;
  2. 解决:使用快速傅里叶变换算法继续求解子问题;
  3. 合并:将两个N/2点离散变换的结果按照蝶形运算的位置关系重新排列成一个N点序列;
Karatsuba大数乘法算法
  1. 分解:n位大数分为两个部分:a+b,其中a为整数幂,然后利用(a+b)*(c+d)=ac+ad+bc+bd;将大数乘法转为4次小规模大数乘法;
  2. 解决:小规模大数乘法继续运用该算法,直到可以简单地计算出结果;
  3. 合并:通过三次加法把小规模大数乘法的结果累加,得到大数乘法的最后结果;

动态规划

概念

“随机应变”的动态规划,是一种常见的算法思想;该算法思想是解决多阶段决策问题常用的最优化理论;

动态规划方法的原理是将多阶段决策问题转化为一系列单阶段决策问题,利用各个阶段之间的递推关系,逐个确定每个阶段的最优化决策,最终得到多阶段决策的最优化决策结果;

动态规划策略所能解决的问题有两个特点:

  1. 最优化原理:如果一个问题的最优子结构是不论过去状态和决策如何,对前面的决策所形成的状态而言,其后的决策必须是最优策略;也就是说,不管之前的决策是否是最优决策,都必须保证从现在开始的决策时在之前已有决策基础上的最优决策;
  2. 无后向性:对于某个选定阶段的子问题来说,它之前的各个阶段的子问题的决策只影响该阶段的决策,对该阶段之后的决策不产生影响。

比如,有A,B,C,D,E五个依次进行的决策,最后得到结果R:

所谓最优化原理,就是说在解决问题时,即便A、B因为采用方法X做决策,并没有达到最优程度,但是从C阶段起,使用Y方法做决策以得到最优解,那么最后得到R依然是对A、B采用X方法所能得到的最优解;

所谓无后向性,就是说A、B两个阶段的决策会影响C的决策,但是不会影响D的决策,影响D的是A、B、C;实际上,更多的是A影响了B,B影响了C,C影响了D这样的,不过A不会影响C;(如有错误不当,请批评指正)

应用步骤
  1. 定义最优子问题:确定问题的优化目标以及如何决策最优解,对决策过程划分阶段;
  2. 定义状态:状态既是决策的对象,也是决策的结果。初始状态经过各个阶段的决策,最后得到的状态就是问题的解;
  3. 定义决策:决策是能使状态发生转变的选择动作,如果备选动作有多个,则决策应该是是阶段结果最优的那一个;
  4. 确定状态转换方程及边界条件:状态转换方程是描述状态转换关系的一系列等式,指示从n-1阶段如何过渡到n阶段;边界条件常常是初始状态的值,即递推关系的初始值,也可以理解为递归过程结束的条件;
举例
装配站问题
  1. 定义最优子问题:将工件从一个装配站移到下一个装配站,直到最后一个装配站完成工件组装;
  2. 定义状态:状态可以定义为s[i,j]定义为通过第i条装配线的第j个装配站所需要的最短时间。也就是位置+所需要时间;
  3. 定义决策:选择在当前工作线上的下一个工作站继续装配还是转移到另一条工作线上的下一个工作站继续装配;
  4. 确定状态转换方程及边界条件:状态转移方程展现了决策:s[1,j]=min(s[1,j-1]+a[1,j],s[2,j-1]+k[1,j]+a[1,j]);边界条件为s[1,1]=k[1,1]+a[1,1];s[2,1]=k[2,2]+a[2,2];其中a[i,j]表示第i条装配线上第j个装配站组装工件所需要的时间;k[i,j]表示从另一条装配线上转移到第i条装配线上第j个装配站所需要的时间;
背包问题
  1. 定义最优子问题:对于一定容量的袋子,选择一件物品将其放入,使得总价值最大,直到不能继续添加物品;
  2. 定义状态:选择某件物品放入某时刻(某容量)的袋子所能获得最大价值,即s[i,j]定义为将第i件物品放入容量为j的袋子中所能获得的最大价值;
  3. 定义决策:判断装入第i件物品能获得收益大还是不装入第i件物品收益大;
  4. 确定状态转换方程及边界条件:记Pi、Vi为第i件物品的价值和体积;s[i,j]=max(s[i-1,j],s[i,j-Vi]+Pi);边界条件为s[0,Vmax]=0;没有放入物品时价值为0;

穷举

概念

“大巧若拙”的穷举算法,也称为穷举搜索法,是在解空间内穷举搜索的一种思想;

关于穷举思想,我觉得配得上“大巧若拙”这个词;在以前,我是看不上穷举这一策略的,把所有的可能都过一遍,寻找出最满足要求的解,这样的策略也算是策略?没错,我只看到了它的“拙”,因为眼界和知识面的狭窄,我没看到它的“巧”;首先,是解空间的定义,穷举不是大海捞针,而是在解空间里探寻答案;穷举也不是漫无目的的搜索,针对解空间需要有一定的搜索策略;实际上,树的遍历就是一种常见的穷举,而树就是解空间,而中序、后序、先序则是一种搜索策略。解空间和搜索策略既是穷举的关键,也是穷举的难点:如何定义解空间?如何针对解空间指定搜索策略?

应用步骤
  1. 确定问题的解的定义,解空间的范围以及正确解的判定条件;
  2. 根据解空间的特点选择搜索策略,检验解空间中的候选解是否正确;

解空间,是所有可能解的集合;所以我们需要有一种方式来描述“所有可能”,如果元素是“二值”的,则可以使用二进制来表示状态;也可以使用向量来描述元素组合;

搜索策略,是关于检查那个(些)候选解的策略;常见的策略有:盲目搜索、启发性搜索、剪枝策略、搜索算法的评估和收敛;

举例
Google方程式

WWWDOT-GOOGLE=DOTCOM,每一个字母代表一个0-9之间的数字,上面的元素都是合法的数字,不以0开头;找出字母与数字之间的对应关系;这一问题,只能穷举来解决;

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值