一起养成写作习惯!这是我参与「掘金日新计划 · 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
}
十进制转换为二进制
将一个十进制数除以二,得到的商再除以二,依此类推直到商等于零为止,倒取除得的余数,即换算为二进制数的结果。只需记住要点:除二取余,倒序排列。
后面算出来的余数排在最前面,这就符合栈的特性,后进先出
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);
}
执行过程:函数调用会在内存形成一个"调用记录",又称"调用栈",保存调用位置和内部变量等信息。
- 执行 sum 函数;
- ……无限循环调用 sum 函数;
- 直到调用记录超过执行栈的最大存储范围,然后系统抛出错误,终止程序。
总结:我们只需要知道栈是一个逻辑结构,不是一个物理结构,知道了这一点就抓住了栈的本质。