如果昨天是资料结构,那今天必然是来讨论演算法啦!
「演算法」是另一个会让许多非本科系的 developer 吓到的词汇,会觉得好像是很高深难懂的技术,应该都是 Google 等级的程序设计师才会用到吧?
但很神奇的是,明明是看起来这麽难的一个词汇,反而满常出现在媒体用语中的:
所以今天让我们来揭开演算法神秘的面纱吧!
跟昨天一样,今天主轴在於让大家初步认识演算法,没有要带着大家到处去刷题,如果有兴趣想用演算法来实际解题,欢迎到我的队友系列文,用 LeetCode 刷题来练习!
由有限步骤所构成的集合,可以用於解决某一个特定的问题。
简单来说,有点像是「解决某个问题的步骤流程」,比如在生活上,我们要做家事洗衣服:
没错,上述五个步骤就是为了完成「洗衣服」这个任务,的其中一种演算法!那有没有其它演算法?
有啊!衣服一定要自己洗吗?
所以,要完成一件事情,演算法不只一种唷!
这只是个范例,衣服请自己洗嘿!
其实,「如何把大象放进冰箱里」这个冷笑话,也是演算法的一种哦:
是不是步骤非常明确,而且能够解决问题呢XD
而在计算机科学的领域,演算法其实也是在做同样的事情,我们透过设计一连串的指令、动作,让电脑去执行,以便协助我们解决一些特定问题。
因此,就算是最简单的:我想条列出商品名称与对应的价钱,都是演算法的一种哦!
const products = [
{ name: 'TV', price: 12000 },
{ name: 'laptop', price: 25000 },
{ name: 'washing machine', price: 8000 },
];
products.forEach(product => console.log(`${product.name} 的价钱是 ${product.price}`));
执行结果
TV 的价钱是 12000
laptop 的价钱是 25000
washing machine 的价钱是 8000
演算法其实就是「解决问题的方法」,所以这个问题应该改成:
解决问题的方法有好坏之分吗?
嗯。。。听起来是有的,只是我们不会说哪个方法绝对好,而是会用两个指标来衡量:动用的资源以及花费的时间。
而这两个指标放在计算机科学领域,就是你曾经听过的:
空间复杂度就是,我用了多少记忆体空间来完成这件事。
比如要把一个阵列的顺序完全反转:
const reverseArr = (input) => {
const output = [];
input.forEach(item => output.unshift(item));
console.log(output);
};
const arr = [1, 2, 3, 4, 5];
reverseArr(arr);
执行结果
[5, 4, 3, 2, 1]
可以看到为了达成「反转」这件事情,我多宣告了一个 output
阵列(花到额外的空间来放),里面总共有五格,所以看起来 reverseArr
的空间复杂度是 5?
感觉怪怪的对吧!因为这个 output
会根据 input
进来的参数大小,而有所不同,因此我们不能够直接说空间复杂度是 5。
我们使用 Big O notation,表达在「最坏情况下的复杂度等级」,如果像上例复杂度会随着 input
线性变动的,会用O(n)
来表示。
那如果不会随着 input
变动的会像这样:
const reverseArr = (input) => {
let temp;
const length = input.length;
for(let i = 0; i<=length/2; i++) {
temp = input[i];
input[i] = input[length-i-1];
input[length-i-1] = temp;
}
console.log(input);
};
const arr = [1, 2, 3, 4, 5];
reverseArr(arr);
执行结果
[5, 4, 3, 2, 1]
可以看到上面的写法,同样是为了达成「反转」这件事情,但我都在 input
本体上面操作,完全没有使用到额外的空间 output
,也就是说,无论 input
的 length
有多大,这个函式都不会花费更多空间,称之为 O(1)
。
其中 1 其实是代表常数,并不是真的只花 1 个空间,而是表达不会随着
input.length
变动
上面还有提到另一个重点是,Big O 代表的是「最坏情况」,会这样说是因为执行一段程序所花费的时间或空间,有可能有三种情况:
比如说,我们要从阵列中找到一个元素,只要找到就可以不用继续找了:
const findElement = (input, element) => {
const length = input.length;
for(let i = 0; i<length; i++) {
if (input[i] === element) break;
}
};
const arr = [1, 2, 3, 4, 5];
findElement(arr, 3);
像上面这种程序,执行 findElement(arr, 3)
只要跑三次回圈就结束了,但如果执行 findElement(arr, 5)
就要跑五次回圈(虽然这个范例的空间复杂度都是一样的)。
因此我们会有上述三种情况,但计算机科学领域特别重视「上限」,才能够确保系统能够负荷最严重的状况,所以我们才会只强调「最坏状况」。
时间复杂度则是,我花了多少时间来完成这件事。
但这边有一点要特别注意,并不是单纯指花了几分钟来执行,因为每台电脑效能不一样,我们不是要衡量哪一台电脑比较快,而是「不同的程序写法在同一台电脑上执行」时的复杂度,因此我们会使用「执行了多少指令」来计算复杂度。
以刚刚上面的例子:
const reverseArr = (input) => {
const output = []; // 1
input.forEach(item => output.unshift(item)); // n
console.log(output); // 1
};
const arr = [1, 2, 3, 4, 5];
reverseArr(arr);
可以看到我在注解的地方写上了复杂度等级,总共合起来是 n+2,我们只取次方最高的 n,因为当 input
增加时,变化最大的会是最高次方的 n,所以 reverseArr
的时间复杂度就是 O(n)
。
可以看到刚刚同样是一个「反转」的任务:
output
这代表时间与空间这两种「效能」是 trade-off,很多时候如果要空间就得花时间,要时间就要花空间。
简直跟租房子一样,要大套房就要增加通勤时间
而这大多时候没有正确答案,只取决於当下的需求,看是牺牲哪一边比较恰当。(当然如果资料量根本不大,基本上电脑也不痛不痒)
这个网站很视觉化呈现了,不同复杂度执行起来的差异:
演算法也是大学资工系基础必修的一环,而且通常会跟资料结构相互配合,毕竟,使用怎样的资料结构,会直接影响到演算法的复杂度,因此资料结构+演算法一起学,报个 2 次铁人赛 60 天应该就可以了XD"
大多数人会觉得演算法很高深莫测,都是高手在用的,但其实经过今天的说明,不难看出其实,1+1 也是相加的一种演算法,演算法就只是达成目标所需的步骤而已。
之所以会看起来高大上,是因为如最後所说的,只有输入资料量庞大的时候,才容易看出演算法好坏的落差,而会碰到庞大输入量的,当然都是高手啦!
所以希望大家不要被「演算法」这个名字吓到,去了解它,并且试着思考自己的程序是否有更好的演算法,即便资料量不大,也能够在思考的过程中成长!
荆棘的路险峻
远绕的路疲倦
在不知道是否存在的交会点
寻一处沙洲
<<: Day25 每年都在烦恼要送什麽礼物给亲朋好友吗?何不送个AR明信片呢!?
>>: Day26:今天来聊一下Hacking Mobile Platforms
今天来介绍晚餐及宵夜场的好地方 靠近行天宫捷运站的糜家庄潮式砂锅 别看照片好像是一锅朴实无华的粥 但...
第八天~ 前面我们把 React Native 一些特色讲解了一下, 也稍微的改动了画面, 那在这过...
前言:昨天简单实作了链结串列,今天要来介绍进阶一点的应用,第一个是利用之前写的get()和set()...
Hello 大家好,我是阳光伏特家的工程师 Oscar,这是我第一次参加铁人赛!每年都想报名结果每年...
摘要 资料集预处理 1.1 ImageDataGenerator 1.2 flow_from_dir...