基础
栈是一种特殊的线性表,它可以用数组或者链表来实现。只能在栈顶操作数据,具有先进后出的特点。
假如有一个序列1,2,3,4,5。将它按顺序加入栈中,它的结构如下图:
现实生活中,我们也能看到很多关于栈的结构,比如说网球桶,你拿到的网球都是桶顶的,放回去的时候也会放到桶顶。
在Js中,像执行上下文也使用栈这种数据结构来维护的,栈底是全局作用域,当前执行代码的执行上下文依次加入栈中,栈顶的元素永远是正在执行的上下文对象。基本数据类型的值也放在栈中。
在canvas标签中,有两个方法save()和restore()也用到了栈这种数据结构,save方法将canvas当前绘图环境的所有属性压栈,之后可以改变一些属性做某些操作,当你想恢复之前保存的绘图环境时,调用restore方法将canvas状态堆栈的顶部条目弹出,就实现了canvas绘图环境的保存和恢复。
栈的实现
我用数组实现了栈这种数据结构,因为js中数组方法实现了push和pop的方法,再来实现栈会非常的简单。
操作栈的方法有:push(将元素加入栈顶),pop(将元素从栈顶删除),top(返回栈顶元素),size(返回栈里元素的个数),isEmpty(判断栈是否为空),clear(清空栈中的元素)。
const Stack = function() {
let items = [];
this.push = function(item) {
items.push(item);
}
this.pop = function() {
return items.pop();
}
this.top = function() {
return items[items.length - 1];
}
this.size = function() {
return items.length;
}
this.isEmpty = function() {
return items.length == 0;
}
this.clear = function() {
items = [];
}
}
栈的应用题
- 括号匹配
合法的括号字符串应该是一个左括号就能匹配到一个对应的右括号。而且字符串中还会出现除了括号以外的的字符。如果用数组来解决这个问题的话,那么我们遍历到一个左括号,之后哪个右括号是它的右括号我们很难寻找。如果用栈来做思路就会非常简单。
遇到左括号,将其压栈;遇到右括号,去栈顶元素,若栈为空,说明它没有对应的左括号,返回false,若栈顶元素是与它匹配的左括号,则继续遍历字符串;若遍历完字符串,栈不为空,则左括号多余了,返回false。
const isMatch = function(string) {
let stack = new Stack();
let obj = {
")": "(",
"}": "{",
"]": "["
}
for (value of string) {
if (value == "(" || value == "[" || value == "{") {
stack.push(value);
} else if (value == ")" || value == "]" || value == "}") {
if (stack.size()) {
let top = stack.pop();
if (top !== obj[value]) {
return false;
}
} else {
return false;
}
}
}
if (stack.size() !== 0) {
return false;
}
return true;
}
2.实现一个有求最小值方法的栈
每次栈中的数据改变,那么这个栈的最小值很可能会改变。我们可以考虑用栈来存储最小值,这个最小值栈会在每次pop和pop时更新,它的栈顶的数据永远是此时数据栈中的最小值。
const minStack = function() {
let items = [];
let min_stack = new Stack();
this.push = function(item) {
if (items.length == 0) {
items.push(item);
min_stack.push(item);
} else {
if (item < min_stack.top()) {
min_stack.push(item);
} else {
min_stack.push(min_stack.top());
}
items.push(item);
}
}
this.pop = function() {
min_stack.pop();
return items.pop();
}
this.min = function() {
return min_stack.pop();
}
}
3.使用非递归的方法实现树的前序遍历
前序遍历的顺序是先遍历当前节点,然后遍历它的左子树,最后再遍历它的的右子树。当遍历完左子树怎么找到右子树是这个问题的关键,我们考虑用栈存储右子树,当遍历完左子树,则弹出栈顶的一个右子树节点继续遍历。同理,迭代法如何实现树的中序遍历和后序遍历是一样的思路,可能实现的时候会比前序遍历难一些。
let pre_order2 = function(node) {
if(node == null){
return;
}
let stack = new Stack();
let curr_node = node;
while (curr_node) {
console.log(curr_node.data);
if (curr_node.rightchild) {
stack.push(curr_node.rightchild);
}
if (curr_node.leftchild) {
curr_node = curr_node.leftchild;
} else {
curr_node = stack.pop();
}
}
}