JavaScript-算法(一)算法复杂度
一、算法复杂度
给定一个或多个已知值,通过一系列的运算输出所需结果的过程 叫 算法 一个需求可能可以通过多种算法来实现 ,学习算法让我们了解怎样的算法效率更高更快,从而让我们针对某个需求做出最合适的选择。
如果你想在编程的道路上走得更远一些,或涉猎到更多的数据逻辑,算法对你的帮助很大~
讨论一个算法的好坏,最主要的两个方面:该算法所需时间 - 时间复杂度 ,该算法占用空间 - 空间复杂度。 耗时短占用低肯定是最优选择,而往往我们需要在两者中找到一个平衡点,作为一名程序开发者,大多数情况下,时间复杂度比空间复杂度的影响会更大一些,能做的改良和优化也更多,所以一般如果不做特别说明,我们讨论算法复杂度的时候都是指时间复杂度
1.时间复杂度
一个算法所需要的时间需要具体上机运行才知道,很显然不可能每个都验证一遍,再做选择,我们可以对算法好事进行合理的预测。
一个算法中语句的执行次数 我们叫做时间频度,记作T(n),n 表示问题规模,n增大T(n) 也随之增大,二我们预估算法的效率,就是对T(n)的变化趋势进行分析,为了方便分析,我们需要简化一下T(n)函数,所以我们引入时间复杂度的概念:若有某个辅助函数f(n),使得当n趋近于无穷大时, T(n)/f(n) 的极限值不等于0的常数,则称f(n) 是 T(n) 的同数量级函数,记作 T(n) = O(f(n)),O(f(n)) 就是时间复杂度的表示法。
总结一下 : 用 O(f(n)) 表示 算法的时间复杂度
时间复杂度 是描述一个算法 随着问题规模不断增大之后的所需要消耗的时间的一个变化趋势
2.复杂度计算 举例
理论上 ,我们对 O(f(n)) 中的 f(n) 没有写法限制 ,但是因为我们关注的是 随 n 增大复杂度的变化趋势,所以各常数项和系数我们是直接省略不考虑的。
简单的例子:
// O(1)
let a = 10
let b = 20
console.log(a+b)
//T(n) = 1 + 1 + 1 = 3 时间频度 (语句执行几次)
//O(1) 时间复杂度 (常数项和系数省略)
// O(n)
function fn(n){
let sum = 0
for (let i = 1; i < n ; i++) {
sum += i
}
return sum
}
// T(n) = 1 + n + 1 = n +2
// O(n) // 只看最高项 不看系数
// O(n^2)
function fn(n){
let a = 0;
for (let i = 1; i < n ; i++) {
for (let j = 1;j<n;j++){
a = i*j
}
}
re function fn(n){
let a = 0;
for (let i = 1; i < n ; i++) {
for (let j = 1;j<n;j++){
a = i*j
}
}
return a
}
// T(n) = 1 + n + n*n + n*n + 1 = 2*n^2 +n +2
// O(n^2)
// O(n^2)
function fn(n){
let a = 0
for (let i = 0; i <= n; i++) {
for (let j=i;j<=n;j++){
a = i*j
}
}
return a
}
// T(n) = 1 + n + n*(1+n)/2 + 1 = 1/2*n^2 + 2/3*n +2
// O(n^2)
// O(n^2)
function fn(n, m) {
let a = 0
for (let i = 0; i < n; i++) {
for (let j = 1; j < m; j++) {
a = i * j
}
}
return a
}
// T(n) = 1 + n + n*m +n*m +1 = 2*n*m + n + 2
// O(n^2) m n 只是一种描述 在计算时间复杂度的时候同样会把它当作n
function fn(n){
let a = 0
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < n; k++) {
a = i* j * k
}
}
}
}
// O(n³)
function fn(n){
let a = 0
for (let i = 1 ; i < n; i*=2) {
a+=i
}
return a
}
// O(log₂n)
function fn(n){
let a =0
for (let i = 1; i <=n ; i++) {
for (let j = 1; j <=n ; j*=2) {
a += i*j
}
}
return a
}
// O(n*log₂n)
斐波那契数列
举一个明显的例子:斐波那契数列: 0 1 1 2 3 5 8 … 如何计算斐波那契数列呢?
这是最简单的实现 但是有一个问题 ,他的时间复杂度及其的恐怖,达到了2^n 。
function Fibonacci(n) {
if (n === 1) return 0
if (n === 2) return 1
// 等于前两个数组的和
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
//O(2^n) 时间复杂度是及其庞大的
console.time('耗时') //耗时: 1139.516845703125 ms
Fibonacci(40)
console.timeEnd('耗时')
对其进一步优化
function Fibonacci(n) {
if (n === 1) return 0
if (n === 2) return 1
let n1 = 0, n2 = 1, cur;
// 保存 n-1 n-2 项
for (let i = 3; i <= n; i++) {
cur = n1 + n2
n1 = n2
n2 = cur
}
return cur
}
// O(n)
console.time('耗时') //耗时: 0.0478515625 ms
Fibonacci(40)
console.timeEnd('耗时')
上述的方式,将其n-1,n-2 项进行了存储 ,不用每次都重新开始计算,有效的节省了开销。可见,算法的魅力还是很大的~
百钱百鸡 问题
百钱百鸡 问题 描述: 100文钱 要买 100 只鸡 小鸡 -- 3只/1文 母鸡 -- 1只/3文 攻击 -- 1只/5文 每种鸡都要有 ,总共刚好100只,刚好花完100文钱, 一共有几种买法
原始方法进行求解:
let x, y, z;// x小鸡 y母鸡 z公鸡
for (let x = 1; x <= 98; x++) {
for (let y = 1; y <= 98; y++) {
for (let z = 1; z <= 98; z++) {
if (x / 3 + y * 3 + z * 5 === 100 && x + y + z === 100) {
console.log(`小鸡${x}只,母鸡${y}只,公鸡${z}只`)
/*
* 小鸡78只,母鸡18只,公鸡4只
* 小鸡81只,母鸡11只,公鸡8只
* 小鸡84只,母鸡4只,公鸡12只
* */
}
}
}
}
// O(n^3)
// 时间复杂度比较高 此时 需要进行改进
如果此时结合我们进一步的分析和思考,可以寻找 x y z,三者之间的共同关系 ,分析思路:
/*
* x + y + z = 100 ①
* x/3 + 3y +5z = 100 ②
*
* ② * 3 =>
* x + 9y + 15z = 300 ③
* ③ - ① =>
* 8y + 14z = 200
* 化简得:
* y = 25 + 7z/4
* 设 n = z/4
* 得 y = 25-7n
* x = 75 + 3n
* z = 4n
*
* 因为 z = 4n 不能大于 100 n<25
* 因为 y = 25 - 7n 不能小于0 n<4
* 因为 x = 75 + 3n 不能大于100 n<8
* */
经过上述 分析与计算 ,对代码进行如下优化:
for (let i = 1; i < 4; i++) {
let x = 75 + 3*i;
let y = 25-7*i;
let z = 4*i;
if (x / 3 + y * 3 + z * 5 === 100 && x + y + z === 100) {
console.log(`小鸡${x}只,母鸡${y}只,公鸡${z}只`)
/*
* 小鸡78只,母鸡18只,公鸡4只
* 小鸡81只,母鸡11只,公鸡8只
* 小鸡84只,母鸡4只,公鸡12只
* */
}
}
// O(n) 时间复杂度
可见 具有算法思维是多么的重要!