了解算法设计原理
算法不一定是一种特殊的操作。它们是概念性的,是您为实现特定目标而在代码中采取的一组步骤。
本文将深入探讨算法设计的原理。如果您不清楚我指的是什么,请继续阅读!
当您听到“算法”一词时,您可能会以以下三种方式之一进行响应:
您会立即了解并理解我们在说什么,因为您学习过计算机科学。
您知道算法是诸如Google和Facebook这样的公司的主力军,但您实际上不确定这个词是什么意思。
您奔跑而躲避恐惧,因为您对算法的了解使您想起了高中微积分的噩梦。
如果您是后两者之一,那么本文适合您。
究竟是什么算法?
算法不一定是一种特殊的操作。它们是概念性的,是您为实现特定目标而在代码中采取的一组步骤。
通常以简单的术语将算法定义为“完成任务的指令”。他们也被称为“食谱”。在《社交网络》中,扎克伯格需要使用算法来使Facemash正常工作。如果您看过电影,可能还记得在Mark宿舍房间的窗户上看到一个简陋的方程式。但是那个草的代数与Mark简单的“热门与否”网站有什么关系?
算法确实是指令。也许更准确的描述是算法是用于以有效方式完成任务的模式。扎克伯格的Facemash是一个投票网站,用于确定某人相对于整个人群的吸引力,但是只会为用户提供两个人之间的选择权。马克·扎克伯格(Mark Zuckerberg)需要一种算法,该算法可以确定哪些人可以相互匹配,以及如何相对于该人的先前历史和先前的竞争者来评估投票。这不仅需要简单地计算每个人的票数,还需要更多的直觉。
例如,假设您要创建一种算法,将1加到任何负数,再从1减去1,对0不做任何事情。您可能会执行以下操作(在JavaScript式伪代码中):
function addOrSubtractOne(number){
if (number < 0) {
return number + 1
} else if (number < 0) {
return number - 1
} else if (number == 0) {
return 0;
}
}
您可能对自己说:“这是一个功能。” 而且你是对的。算法不一定是一种特殊的操作。它们是概念性的,您可以通过代码中的一系列步骤来实现特定的目标。
那么为什么他们有什么大不了的呢?显然,对数字加1或减1是一件相当简单的事情。
但是,让我们先谈一下搜索。要搜索一组数字中的数字,您会如何考虑?天真的方法是迭代数字,将每个数字与您要搜索的数字进行比较。但这不是一个有效的解决方案,并且可能的完成时间范围非常广,因此在扩展到大型搜索集时,这是一种不稳定且不可靠的搜索方法。
function naiveSearch(needle, haystack){
for (var i = 0; i < haystack.length; i++){
if (haystack[i] == needle) { return needle; }
}
return false;
}
幸运的是,我们可以为此做得更好。
为什么效率低下?
对算法有深刻的理解和鉴赏,没有比成为更好的算法设计者更好的方法了。
假设您的数组有50,000个条目,并且您进行了蛮力搜索(即通过迭代整个数组进行搜索)。在最佳情况下,您要搜索的条目将是50,000个条目数组中的第一个条目。但是,在最坏的情况下,该算法完成的时间将比在最坏的情况下长50,000倍。
那有什么更好的呢?
相反,您将使用二进制搜索进行搜索。这涉及到对数组进行排序(我将让您自己学习),然后将数组分为两半,并检查搜索数是否大于或小于数组中途标记。如果它大于已排序数组的中途标记,那么我们知道前半部分可以被丢弃,因为搜索到的数字不属于数组的一部分。我们还可以通过定义数组的外部边界并检查查找的数字是否存在于这些边界之外来进行大量工作,如果存在,我们将进行多次迭代操作并将其翻转一次迭代操作(在蛮力算法中,该操作将执行50,000次操作)。
sortedHaystack = recursiveSort(haystack);
function bSearch(needle, sortedHaystack, firstIteration){
if (firstIteration){
if (needle > sortedHaystack.last || needle < sortedHaystack.first){
return false;
}
}
if (haystack.length == 2){
if (needle == haystack[0]) {
return haystack[0];
} else {
return haystack[1];
}
}
if (needle < haystack[haystack.length/2]){
bSearch(needle, haystack[0..haystack.length/2 -1], false);
} else {
bSearch(needle, haystack[haystack.length/2..haystack.length], false);
}
}
听起来很复杂
考虑单个二进制搜索算法看似复杂的性质,并将其应用于数十亿个可能的链接(通过Google搜索)。除此之外,让我们对链接的搜索应用某种排名系统,以给出响应页面的顺序。更好的是,基于人工智能社交模型应用某种看似随机的“建议”系统,该系统旨在识别您可能希望添加为朋友的人。
这使我们对为什么算法不仅仅是函数的奇特名称有了更清晰的了解。在最好的情况下,它们是聪明,有效的方法来完成某些工作,而这些方法比最明显的解决方案需要更高的直觉。他们可以花上数年的时间才能完成一台超级计算机的工作,然后将其变成一项在手机上只需几秒钟即可完成的任务。
算法如何应用于我?
对于我们大多数开发人员而言,我们并不是每天都在设计高级抽象算法。
幸运的是,我们站在我们之前的开发人员的肩膀上,他们编写了本机排序函数,并允许我们以高效的方式在字符串中搜索带有indexOf的子字符串。
但是我们确实要处理我们自己的算法。我们for每天创建循环并编写函数;那么良好的算法设计原则如何可以帮助编写这些函数?
了解您的输入
算法设计的主要原则之一是,如果可能,以某种方式构建算法,使输入本身可以为您完成一些工作。例如,如果您知道输入将始终是数字,则无需对字符串进行例外/检查,也不必将值强制转换为数字。如果您知道DOM元素for在JavaScript中的每次循环中都是相同的,则不应该在每次迭代中都查询该元素。同样,在for循环中,如果可以使用(更接近)简单的操作来完成相同的事情,则不应使用带有开销的便捷函数。
// don't do this:
for (var i = 1000; i > 0; i--){
$("#foo").append("<span>bar</span>");
}
// do this instead
var foo = $("#foo");
var s = "";
for(var i = 1000; i > 0; i--){
s += "<span>bar</span>";
}
foo.append(s);
如果您是JavaScript开发人员(并且使用jQuery),并且您不知道上述函数的功能以及它们之间的显着不同,那么接下来要讲的就是您。
了解您的工具
在最好的情况下,[算法]是一种聪明,有效的方法来完成某些工作,而这种方法比最明显的解决方案需要更高的直觉。
不言而喻,这很容易想到。但是,“知道如何编写jQuery”和“了解jQuery”之间是有区别的。了解您的工具意味着您立即了解每行代码的作用(立即(函数的返回值或方法的效果)和隐式的(与运行库函数相关的开销多少),或者哪一种是最有效的连接字符串的方法)。要编写出色的算法,重要的是要了解低级函数或实用程序的性能,而不仅仅是它们的名称和实现。
了解环境
设计高效的算法是一项全力以赴的工作。除了将您的工具理解为一个独立的部分以外,您还必须了解它们与现有大型系统交互的方式。例如,要完全了解特定应用程序中的JavaScript,了解跨浏览器场景中JavaScript的DOM和性能,可用内存如何影响渲染速度,您可能与之交互的服务器结构(及其响应),以及许多其他无形的考虑因素,例如使用情况。
减少工作量
通常,算法设计的目标是用更少的步骤来完成一项工作。(有一些例外,例如Bcrypt哈希。)在编写代码时,请考虑计算机为达到目标所执行的所有简单操作。这是一个简单的清单,可开始着手进行更有效的算法设计:
使用语言功能来减少操作(可变缓存,链接等)。
尽可能减少迭代循环嵌套。
尽可能在循环之外定义变量。
使用自动循环索引(如果有)而不是手动索引。
使用巧妙的归约技术,例如递归除法和征服以及查询优化,以最小化递归过程的大小。
学习先进技术
对算法有深刻的理解和鉴赏,没有比成为更好的算法设计者更好的方法了。
每周花一两个小时阅读《计算机编程的艺术》。
尝试进行Facebook编程挑战赛或Google Codejam。
学习使用不同的算法技术解决相同的问题。
通过.sort()较低级别的操作实现语言的内置功能(例如)来挑战自己。
结论
如果您不了解本文开头的算法是什么,那么希望现在,您对这个难以捉摸的术语有了更具体的了解。作为专业开发人员,重要的是我们了解可以对所编写的代码进行分析和优化,并且花时间对代码的性能进行分析也很重要。