JS 数据结构 —— 栈

588 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。

最近学习了一些 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 出栈。图示如下: image.png

封装

下面基于数组用构造函数封装一个栈结构:

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
}

感谢.gif 点赞.png