JavaScript数据结构——栈

139 阅读3分钟

前言

嘿,掘友们!今天我们来了解并实现数据结构——栈。

本文内容包括

  • 什么是栈
  • 创建一个基于数组的栈
  • 创建一个基于对象的栈
  • 保护数据结构内部元素
  • 用栈解决实际问题
    • 十进制转二进制
    • 进制转换算法

什么是栈

栈是一种遵从后进先出(LIFO)原则的有序集合。新添加和待删除的元素都保存在栈的同一端,称作栈顶。另一段叫栈底。新元素靠近栈顶,旧元素接近栈底。

img

图片来源:百度百科

我们要为栈声明一些方法:

  • push(element):添加一个新元素到栈顶
  • pop():移除栈顶元素
  • peek():返回栈顶元素,不对栈做任何修改
  • isEmpty():判断栈内是否为空,空返回true,否则返回false
  • clear():移除栈内所有元素
  • size():返回栈内元素个数
  • print():以字符串形式输出栈内元素,每个元素用,相隔

创建一个基于数组的栈

class Stack {
  constructor() {
    this.items = []
  }
  push(element) {
    this.items.push(element)
  }
  pop() {
    return this.items.pop()
  }
  peek() {
    return this.items[this.items.length]
  }
  isEmpty() {
    return this.items.length === 0
  }
  clear() {
    this.items = []
  }
	size() {
    return this.items.length
  }
  print() {
    if(this.isEmpty()) return ''
    return this.items.join(',')
  }
}

创建一个基于对象的栈

class Stack {
  constructor() {
    this.count = 0
    this.items = {}
  }
  push(element) {
    this.items[this.count] = element
    this.count++
  }
  pop() {
    if(this.isEmpty()) return undefined
    this.count--
    let r = this.items[this.count]
    delete this.items[this.count]
    return r
  }
  isEmpty() {
    return this.count === 0
  }
  peek() {
    if(this.isEmpty()) return undefined
    return this.items[this.count - 1]
  }
  clear() {
    this.count = 0
    this.items = {}
  }
  size() {
    return this.count
  }
  print() {
    if(this.isEmpty()) return undefined
    let string = `${this.items[0]}`
    for (let i = 1; i < this.count; i++) {
      string = `${string}, ${this.item[i]}`
    }
    return string
  }
}

保护数据结构内部元素

我们希望保护内部的元素,只有暴露出的方法才能修改内部结构。我们Stack 类中声明的 items 和 count 属性并没有得到保护。

尽管基于原型的类能节省内存空间并在扩展方面由于基于函数的类,但这种方式不能声明私有属性或方法。

下划线命名约定

class Stack {
  constructor() {
    this._count = 0
    this._items = {}
  }
}

下划线命名只是一种约定,并不能保护数据,只能依赖于开发者具备的常识。

限定作用域Symbol实现类

const _items = Symbol('stackItems')
class Stack {
  constructor() {
    this[_items] = []
  }
}

在类中要访问 _items ,只需要把所有的 this.items 都换成 this[_items]

不过这种方法创建了一个假的私有属性。因为我们可以通过 `Object.getOwnPropertySymbols

方法能够取到类里面声明的所有Symbols属性。

// 破坏Stack类
const stack = new Stack()
stack.push(5)
stack.push(4)
let objectSymbols = Object.getOwnPropertySymbols(stack)
console.log(objectSymbols.length)	// 1
console.log(objectSymbols)	// [Symbol()]
console.log(objectSymbols[0])	// Symbol()
stack[objectSymbols[0]].push(3)
stack.print() // 5, 4, 3

可以通过上面的代码 stack[objectSymbols[0]]得到 _items_items属性是一个数组,可以进行任意的数组操作。

WeakMap实现类

const items = new WeakMap()
class Stack {
  constructor() {
    items.set(this, [])
  }
  push(element) {
    let s = items.get(this)
    s.push(element)
  }
  pop() {
    let s = items.get(this)
    let r = items.pop()
    return r
  }
}

现在 items 在Stack类里是真正的私有属性。采用这种方法,代码的可读性不强,在扩展时无法继承私有属性。

用栈解决实际问题

十进制转二进制

// 使用数组栈
function decimalToBinary(decNumber) {
  const stack = new Stack()
  let number = decNumber
  let rem
  let string = ''
  
  while(number > 0) {
    rem = Math.floor(number % 2)
    stack.push(rem)
    number = Math.floor(number / 2)
  }
  
  while(!stack.isEmpty()) {
    string += stack.pop().toString()
  }
  
  return string
}

进制转换算法

// 十进制转换成基数为2-36的任意进制
function baseConverter(decNumber, base) {
  const stack = new Stack()
  let number = decNumber
  let rem
  let digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let string = ''
  
  if(!base >= 2 && base <= 36) return ''
  
  while(number > 0) {
    rem = Math.floor(number % base)
    stack.push(rem)
    number = Math.floor(number / base)
  }
  
  while(!stack.isEmpty()) {
    string += digits[stack.pop()]
  }
  
  return string
}

结语

栈是数据结构中最基础的一种。他遵循后进先出的原则。栈也用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录(浏览器的返回按钮)。