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