从数据结构与算法看优秀编码习惯(一) | 青训营笔记

173 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记

良好的代码风格和代码习惯无论是对团队合作还是程序效率都有着巨大帮助。本文将从一名算法竞赛选手的经历出发,以数据结构与算法的视角抛出一些优秀编码习惯的思考。

时刻思考复杂度

考虑下面这段代码,非常简单。注册了一个回调函数监听VT的timeUpdate事件,在列表中找到满足下界的首个元素。在浏览器环境中运行似乎也很正常。但当数据量增加时,性能却急剧下降,卡顿掉帧明显。这是因为JavaScript内建的findIndex是O(N)O(N)的复杂度,对于浏览器视频这种触发频繁的事件,每次轮询都全表扫描无疑是不可接受的,尽管代码非常简洁。

this.VT.ontimeupdate = () => {
    if (Array.isArray(this.state.danmakuList) && this.state.screen) {
        const newIndex = this.state.danmakuList.findIndex((element) => {
                return element.progress > Math.floor(this.VT.currentTime * 1000)}
        )
    }
}

解决方法有两类,首先是扫描复杂度的直接优化。注意到我们的查询关键字是时间戳,在列表中表现为递增,我们可以通过自己实现二分查找,将O(N)O(N)的查找降低到O(logN)O(\log N),在这个场合下,性能已经会有明显的提升,对于几k到几w的中小规模数据,O(logN)O(\log N)的overhead可以忽略不记。整体时间复杂度也就从O(TN)O(TN)降低到O(TlogN)O(T\log N)

但我们依然不太满意,如此频繁的事件触发大多会扑空:我们传入的数据非常稀疏,大部分查询请求都会无功而返。能不能做更进一步的优化呢?答案是可以的。通过记录下一条数据的时间戳,我们可以直接在大部分事件回调中避免查询,而每条数据只会被访问一次,我们就将最终复杂度降低到了O(T+N)=O(T)O(T+N)=O(T)。我们将整体的时间复杂度再降低了一个数量级(大概是一个log),从而可以应对更大规模的数据!

在下篇文章中,我将介绍一些和代码风格相关的习惯,他们可能同时影响性能和团队合作效率!