栈是什么?

121 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

定义

栈是一个后进先出的数据结构。

在前端开发中,我们听到最后的就是调用栈,它的特定是最后调用的函数最先执行,也就是后进先出。

栈和数组的关系

首先需要强调栈和数组没有任何关系,就好比一个牛车和汽车的区别,两者没有可比性,都不是同一类东西。

首先栈是一个逻辑结构,一个理论模型,而数组是一个物理结构。我们可以用数组来实现一个栈,也可以用链表来实现一个栈。

数组入栈出栈的api介绍

  • pop: 用于删除数组的最后一个元素,并返回被删除的元素
  • push: 用于向数组末尾添加一个或者多个元素,并返回新的长度
  • shift: 用于删除数组的第一个元素,并返回被删除的元素
  • unshift: 向数组的开头添加一个或者多个元素,并返回新的长度

例子

判断字符串是否括号匹配

  • 一个字符串s可能包含有(){}[]三种括号
  • 判断s是否是括号匹配
  • 如(a{b}c)匹配,而{a(b}}就是不匹配

思路

  • 遇到左括号[{(就入栈
  • 遇到有括号]})就判断栈顶元素是否匹配,如果匹配就出栈
  • 最后判断length是否为0, 如果是0表示字符串是括号匹配
function isMatch(left: string, right: string): boolean {
  if (left === '{' && right === '}') return true
  if (left === '[' && right === ']') return true
  if (left === '(' && right === ')') return true
  return false
}

export function matchBracket(str: string): boolean {
  const length = str.length
  if (length === 0) return true

  const stack = []

  const leftSymbols = '{[('
  const rightSymbols = '}])'

  for (let i = 0; i < length; i++) {
    const s = str[i]
    // includes方法来查找,其实也是遍历数组,时间复杂度是O(n),但是leftSymbols是一个固定的值,和传入的n没有关系,所以整体来看时间复杂度就是一个for循环,即O(n)
    // 空间复杂度也是O(n),因为创建了一个数组stack
    if (leftSymbols.includes(s)) {
      // 左括号,压栈
      stack.push(s)
    } else if (rightSymbols.includes(s)) {
      // 右括号,判断栈顶(是否出栈)
      const top = stack[stack.length - 1]
      if (isMatch(top, s)) {
        stack.pop()
      } else {
        // 只要有一个不匹配,那么就可以判断是不满足的
        return false
      }
    }
  }
  return stack.length === 0
}

十进制转换为二进制

将一个十进制数除以二,得到的商再除以二,依此类推直到商等于零为止,倒取除得的余数,即换算为二进制数的结果。只需记住要点:除二取余,倒序排列。

image.png

后面算出来的余数排在最前面,这就符合栈的特性,后进先出

function binary(num) {
  let y = null
  const stack = []
  while (num > 0) {
    // 商
    let s = Math.floor(parseFloat(num / 2))
    // 余数
    y = parseInt(num % 2)
    num = s
    stack.push(y)
  }
  return stack.reverse().join('')
}

栈溢出

栈溢出一般指的是,我们定义的数据所需要占用的内存超过了栈的大小时,就会发生栈溢出。

如前端常见的执行栈溢出:

function sum(a){
  sum(a);
  console.log(1);
}

执行过程:函数调用会在内存形成一个"调用记录",又称"调用栈",保存调用位置和内部变量等信息。

  1. 执行 sum 函数;
  2. ……无限循环调用 sum 函数;
  3. 直到调用记录超过执行栈的最大存储范围,然后系统抛出错误,终止程序。

总结:我们只需要知道栈是一个逻辑结构,不是一个物理结构,知道了这一点就抓住了栈的本质。