前言
嘿,掘友们!今天我们来了解并实现数据结构——栈。
本文内容包括
- 什么是栈
- 创建一个基于数组的栈
- 创建一个基于对象的栈
- 保护数据结构内部元素
- 用栈解决实际问题
- 十进制转二进制
- 进制转换算法
什么是栈
栈是一种遵从后进先出(LIFO)原则的有序集合。新添加和待删除的元素都保存在栈的同一端,称作栈顶。另一段叫栈底。新元素靠近栈顶,旧元素接近栈底。
图片来源:百度百科
我们要为栈声明一些方法:
- 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
}
结语
栈是数据结构中最基础的一种。他遵循后进先出的原则。栈也用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录(浏览器的返回按钮)。