小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。
最近学习了一些 js 数据结构与算法的知识,在此分享一波。本篇主要介绍一下关于栈结构的内容,有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~
栈是一种常见的数据结构,是一种受限的线性表(n 个具有相同特性的数据元素的有限序列),不同于数组可以在任意位置插入或删除数据,栈只允许在一端,也就是栈顶进行插入或删除的操作,遵循后进先出(last in first out,简称 LIFO)的原则,先进栈(入栈/压栈)的后出栈(退栈/弹栈)。
调用栈(Call Stack)
js 引擎在追踪函数执行流程时采用的机制 —— 调用栈,就用到了栈结构。当执行环境里有多个函数被调用,通过调用栈能够追踪到当前正在执行哪个函数,该函数又调用了哪个函数。比如有如下代码:
function a() {
console.log('函数 a 开始执行')
b()
console.log('函数 a 执行完毕')
}
function b() {
console.log('函数 b 被调用执行了')
}
a()
上述代码运行时,一开始,函数的声明是不会放入调用栈的,直至运行到第 11 行, 函数 a 被调用,于是将 a()
放入调用栈。然后开始执行函数 a 的函数体,执行到第 3 行,发现调用了函数 b,于是将函数 b 入栈。执行完函数 b,再将函数 b 出栈。回到函数 a 继续执行,执行完后将函数 a 出栈。图示如下:
封装
下面基于数组用构造函数封装一个栈结构:
function Stack() {
// 准备个数组
this.items = []
// 入栈
Stack.prototype.push = function (item) {
this.items.push(item)
}
// 出栈
Stack.prototype.pop = function () {
return this.items.pop()
}
// 判断栈是否为空
Stack.prototype.isEmpty = function () {
return this.items.length === 0
}
}
注意这里是将方法定义在了原型 prototype
上,而不是直接 this.push = function() {}
,这样的好处在于更节省内存空间,因为如果直接用 this
,那么每个 new
出来的实例都会有 push
方法,而定义在原型上则是所有实例共用 push
方法。
案例
将十进制转换为二进制。原理就是将十进制的数去除与 2,可以得到一个商和余数,将余数入栈,将商向下取整继续去除与 2,直至得到的商向下取整后为 0 为止,此时将栈中的余数进行出栈,拼接得到的字符串的值就是转换的二进制的值:
function decimalToBinary(num) {
const stack = new Stack()
while (num > 0) {
// 将余数放入栈中
stack.push(num % 2)
// 将 num 重新赋值
num = Math.floor(num / 2)
}
// 将栈中的每一项依次取出获得转换结果
let result = ''
while (!stack.isEmpty()) {
result += stack.pop()
}
return result
}