JS 实现栈结构

3,174 阅读5分钟

JS实现栈结构

一、前言

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表,是一种先进后出(FILO)的数据结构,限定只能在一端进行插入和删除操作,允许操作的一端称为栈顶,不允许操作的称为栈底这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

想要使用js实现栈结构的话,需要保证实现的栈具备以下基础功能:

  • push 新增元素

  • pop 删除栈顶元素

  • peek 返回栈顶的元素

  • clear 清空栈

  • size 栈的大小,也就是元素个数

  • isEmpty 栈是否为空

    清楚了要实现的功能之后,就可以开始栈的实现了

二、栈的实现

1. 栈基础功能实现
class Stack {
  constructor() {
    this.items = []
  }
  // 新增元素
  push(el) {
    this.items.push(el)
  }
  // 删除栈顶的元素并返回其值
  pop() {
    return this.items.pop()
  }
  // 返回栈顶的元素
  peek() {
    return this.items[this.items.length - 1]
  }
  // 清空栈
  clear() {
    this.items = []
  }
  // 栈的大小
  size() {
    return this.items.length
  }
  // 栈是否为空
  isEmpty() {
    return this.items.length === 0
  }
}

const stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack)
stack.items.push(4)
console.log(stack)

以上代码虽然实现了栈的基础功能,但是可以发现外界其实可以直接访问到items变量,然后直接进行修改,这显然是不合理的,正常情况下,外界只能通过栈提供的方法来修改栈,而不能直接访问栈。所以需要将items变量(即栈数组)私有化,使外界无法直接访问。

2. Symbol实现栈数组私有化?
const Stack = (function () {
  const _items = Symbol()
  return class {
    constructor() {
      this[_items] = []
    }
    // 新增元素
    push(el) {
      this[_items].push(el)
    }
    // 删除栈顶的元素并返回其值
    pop() {
      return this[_items].pop()
    }
    // 返回栈顶的元素
    peek() {
      return this[_items][this[_items].length - 1]
    }
    // 清空栈
    clear() {
      this[_items] = []
    }
    // 栈的大小
    size() {
      return this[_items].length
    }
    // 栈是否为空
    isEmpty() {
      return this[_items].length === 0
    }
  }
})()

const stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack)
stack[_items].push(4) // Uncaught ReferenceError: _items is not defined
console.log(stack)

可以看到现在的确无法直接通过stack[_items]来直接访问栈数组,会报错。但是并不意味着其他方法就不行:

const key = Object.getOwnPropertySymbols(stack)[0]
stack[key].push(4)
console.log(stack)

Object对象上提供了一个叫getOwnPropertySymbols的方法,该方法可以获取对象属性中以symbol命名的键名,通过它,就可以获取该栈的symbol值,从而直接访问到栈,所以通过symbol还是无法实现私有化。

3. WeakMap实现栈数组私有化
const Stack = (function () {
  const _items = new WeakMap()
  return class {
    constructor() {
      _items.set(this, [])
    }
    // 新增元素
    push(el) {
      _items.get(this).push(el)
    }
    // 删除栈顶的元素并返回其值
    pop() {
      return _items.get(this).pop()
    }
    // 返回栈顶的元素
    peek() {
      return _items.get(this)[_items.get(this).length - 1]
    }
    // 清空栈
    clear() {
      _items.set(this, [])
    }
    // 栈的大小
    size() {
      return _items.get(this).length
    }
    // 栈是否为空
    isEmpty() {
      return _items.get(this).length === 0
    }
  }
})()

const stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
console.log(stack.size()) // 3
console.log(stack.isEmpty()) // false
stack.clear()
console.log(stack.size()) // 0
console.log(stack.isEmpty()) // true
4. 栈实现十进制转换

js中,提供了Number.prototype.toString方法将十进制转换成其他进制,其实我们自己也可以使用栈这种数据结构,实现十进制的转换。

1)十进制转二进制
10 / 2 = 5,余数为 0

5 / 2 = 2,     余数为 1

2 / 2 = 1,     余数为 0

1 / 2 = 0,     余数为 1

所以十进制数字10转成二进制,就成了 1010,可以发现这种结构就是一个栈,余数挨个入栈,然后再从栈顶挨个将余数取出,进行拼接,就得到了二进制,接下来使用代码实现:

function baseConversionBy2 (num) {
  const stack = new Stack()
  let res = ''

  while (num > 0) {
    stack.push(num % 2)
    num = Math.floor(num / 2)
  }
  while (!stack.isEmpty()) {
    res += stack.pop()
  }
  
  return res
}
console.log(baseConversionBy2(10)) // 1010
2)十进制转八进制
32 / 8 = 4,余数为 0

4 / 8 = 0,     余数为 4

转换为八进制就是40代码实现如下:

function baseConversionBy8 (num) {
  const stack = new Stack()
  let res = ''

  while (num > 0) {
    stack.push(num % 8)
    num = Math.floor(num / 8)
  }
  while (!stack.isEmpty()) {
    res += stack.pop()
  }
  
  return res
}
console.log(baseConversionBy8(32)) // 40
3)十进制转16进制
960 / 16 = 60,余数为 0

60 / 16 = 3,      余数为 12

3 / 16 = 0,     余数为3

16进制有所不同,当余数大于9时,就使用字母代替,10,11,12,13,14,15分别对应a,b,c,d,e,f,所以此处转换为16进制就变成了3c0,代码实现如下:

function baseConversionBy16 (num) {
  const stack = new Stack()
  let res = '',
      digits = '0123456789abcde'

  while (num > 0) {
    stack.push(num % 16)
    num = Math.floor(num / 16)
  }
  while (!stack.isEmpty()) {
    res += digits[stack.pop()]
  }
  
  return res
}
console.log(baseConversionBy16(960)) // 3c0
4)十进制转其他进制

可以发现,十进制转成2、8、16进制都是有规律的,所以以上方法完全可以合并为一个方法,代码如下:

function baseConversionByAuto (num, base) {
  const stack = new Stack()
  let res = '',
      digits = '0123456789abcde'

  while (num > 0) {
    stack.push(num % base)
    num = Math.floor(num / base)
  }
  while (!stack.isEmpty()) {
    res += digits[stack.pop()]
  }
  
  return res
}

console.log(baseConversionByAuto(10, 2)) // 1010
console.log(baseConversionByAuto(32, 8)) // 40
console.log(baseConversionByAuto(960, 16)) // 3c0
5. 栈判断平衡括号

平衡括号

"{[()]}" 属于平衡括号

"{([)]}" 不属于平衡括号

"{{[}]}" 不属于平衡括号

"{{([](#))}()}" 属于平衡括号

open = '{[('

close = ')]}'

open中包含的左半边符号入栈,如果出栈顺序和close包含的右半边符号一样,就可以认为是平衡括号

代码实现:

function check(str) {
  const stack = new Stack()
  const open = "{[("
  const close = "}])"
  let balanced = true
  let index = 0
  let symbol
  let top
​
  while (index < str.length && balanced) {
    symbol = str[index]
​
    if (open.includes(symbol)) {
      stack.push(symbol)
    } else {
      top = stack.pop()
      if (open.indexOf(top) !== close.indexOf(symbol)) {
        balanced = false
      }
    }
    index ++
  }
​
  if (balanced && stack.isEmpty()) {
    return true
  }
​
  return false
}
​
console.log(check("{[()]}")) // true
console.log(check("{([)]}")) // false
console.log(check("{{[}]}")) // false
console.log(check("{{([][])}()}")) // true