头图

🧑‍💻JavaScript算法与数据结构-HowieCong

务必要熟悉JavaScript使用再来学!

一、时间复杂度

(1)下面代码,一共执行了几次?
function traverse(arr){
    // 最没有悬念的是函数里面的第一行代码,只会被执行1次
    var len = arr.length
    // 1. i的初始化语句,只有一次,只会被执行1次
    // 2. i < len,在所有for循环里面,判断语句都会比递增语句多执行一次,所以这里的判断语句执行次数就是n+1
    // 3. i++,跟着整个循环体,毫无疑问会被执行n次
    for(var i = 0; i < len; i++){
        // for循环了n次,所以这条语句就被执行n次
        console.log(arr[i])
    }
}
  • 总次数T(n) = 1+n+1+(n+1)+n = 3n+3
(2)规模为n*n的二维数组的遍历,一共需要执行多少次代码;
function traverse(arr){
    // 只会被执行1次
    var outLen = arr.length
    // 1. i的初始化,执行一次
    // 2. i < outLen,执行n+1次
    // 3. i++,执行n次
    for(var i = 0; i < outLen; i++){
        //循环n次,执行n次
        var inLen = arr[i].length
        
        // 1. j初始化,执行n次
        // 2. j < inLen,执行n(n+1)次
        // 3. j++,执行n*n次
        for(var j = 0; j < inLen; j++){
            // 因为是两层循环,所以这货会被执行n*n = n^2
            console.log(arr[i][j])
        }
    }
}
  • 总次数T(n)= 1+1+(n+1)+n+n+n+n(n+1)+n*n+n^2 = 3n^2+5n+3
  • (3)代码执行次数,可以反映出代码的执行时间,但是如果每次都逐行计算T(n),事件会变得非常麻烦;算法的时间复杂度,它反映的不是算法的逻辑代码到底被执行了多少次,而是随着输入规模的扩大,算法对应的执行总次数的一个变化趋势。要想反映趋势,直接抓住主要矛盾,可以对T(n)做处理

    • 若T(n)是常数,那么简化为1
    • 若T(n)是多项式,比如3n^2+5n+3,我们只保留次数最高那一项,并且将常数系数无脑改为1
    • T(n) = 10 => O(n) = 1
    • T(n) = 3n^2 + 5n + 3 => O(n) = n^2
  • (4)思路仍然是计算T(n) => 推导O(n),实际操作中,O(n)基本可以目测,比如上面的两个遍历函数,遍历N维数组,需要N层循环,只需要关心其最内层那个循环体被执行多少次就可以了

    • 规模为n的一维数组,最内层的循环会执行n次,其对应的时间复杂度为O(n)
    • 规模为nn的二维数组遍历时,最内层的循环会执行nn次,其对应的时间复杂度为O(n^2)
    • 以此类推,规模为nm的二维数组,最内层的循环会被执行nm次,其对应的时间复杂度就是O(nm);规模为nn*n的三维数组最内层循环会执行n^3次,因此对应的空间复杂度为O(n^3)
function traverse1(arr){
    var len = arr.length
    for(var i = 0; i < len; i++){
        console.log(arr[i])
    }
}

function traverse2(arr){
    var outLen = arr.length
    for(var i = 0; i < outLen; i++){
        var inLen = arr[i].length
        for(var j = 0; j < inLen; j++){
            console.log(arr[i][j])
        }
    }
}
  • (5)常见的时间复杂度表达,除了多项式以外,还有logn

    • 这个算法读取一个一维数组作为入参,然后对其中的元素进行跳跃式的输出。这个跳跃的规则,就是数组下标从1开始,每次会乘以2
    function fn(arr){
        var len = arr.length
    
        for(var i = 1; i < len; i = i*2){
            console.log(arr[i])
        }
    }
    • 在有循环的地方,我们关心的永远是最内层的循环体,这个算法,我们关心console.log(arr[i])到底被执行多少次,也就是知道i\<n(len === n)这个条件是在i递增多少次后才不成立的
    • 假设 i 在以 i = i*2的规则递增了x次之后,i\<n开始不成立(反过来说也就是 i >= n成立)。此时我们计算的就是 2^x >= n;x解出来,就是大于等于以2为底数为n的对数:x >= log2n
    • 只有当x小于log2n的时候,循环才是成立的、循环体才能执行,涉及到对数的时间复杂度,底数和系数都是要被简化掉的,那么这里O(n) = logn
  • (6)常见的时间复杂度按照从小到大的顺序排列

image.png

二、空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。时间复杂度相似,它是内存增长的趋势。常见的空间复杂度有O(1)、O(n)和O(n^2)
  • 理解空间复杂度,如下例子:
function traverse(arr){
    var len = arr.length
    for(var i = 0; i < len; i++){
        console.log(arr[i])
    }
}
  • 在函数traverse,占用空间有以下变量

    • arr,len,i
  • 这里的arr,并不是一个不变的数组,arr最终大小是由输入的n的大小决定的,会随着n的增大而增大,呈现一个线性关系,因此这个算法的空间复杂度就是O(n)
  • 假如需要初始化的是一个规模为n*n的数组,那么它的空间复杂度就是O(n^2)

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong


HowieCong
2 声望0 粉丝

大前端开发 => AI 小菜鸡!虚心好学!欢迎一起交流!