什么是栈
# 什么是栈
栈(Stack) 是一种遵循 LIFO(后进先出)原则的线性数据结构 —— 最后放进去的元素,最先被取出来,就像一叠碟子,只能从顶部操作。
核心操作只有三个:push(入栈)、pop(出栈)、peek(查看栈顶)。
在 JS 中,数组就可以当栈用。
# 栈在 JS 中的常见应用
函数调用栈:JS 会用调用栈记录函数的执行顺序,哪个函数最后被调用,就会最先执行完并弹出。
递归:每次递归调用都会进入调用栈,递归返回时再按后进先出的顺序逐层退出。
括号匹配:遇到左括号入栈,遇到右括号就和栈顶匹配,用来判断括号是否合法。
撤销/重做:把每一步操作压入栈中,撤销时弹出最近一次操作,重做时再放回另一个栈。
深度优先搜索 DFS:用栈保存待访问节点,每次取出最后加入的节点继续向深处遍历。
表达式求值:用栈保存数字和运算符,按照优先级逐步计算表达式结果。
浏览器前进/后退:可以用两个栈分别保存后退历史和前进历史,实现页面切换。
# 栈的代码实现
function Stack() {
this.dataStore = []; // 保存栈内元素
this.top = 0; // 标记可以插入新元素的位置,栈内压入元素该变量变大,弹出元素该变量变小
this.push = push; // 入栈操作
this.pop = pop; // 出栈操作
this.peek = peek; // 返回栈顶元素
this.clear = clear; // 清空栈
this.length = length; // 栈的长度
this.isEmpty = isEmpty; // 判断栈是否为空
}
// 向栈中压入元素,同时让指针 top+1,一定注意++
function push(element) {
this.dataStore[this.top++] = element;
// this.dataStore.push(element);
}
// 出栈操作,同时将 top-1
function pop() {
return this.dataStore[--this.top];
// return this.dataStore.pop();
}
// 返回栈顶元素,top-1,返回不删除
function peek() {
return this.dataStore[this.top - 1];
// return this.dataStore[this.dataStore.length - 1];
}
// 返回栈内元素个数
function length() {
return this.top;
// return this.dataStore.length;
}
// 清空栈
function clear() {
this.top = 0;
// this.dataStore = [];
}
// 判断栈是否为空
function isEmpty() {
return this.top === 0;
// return this.dataStore.length === 0;
}
var stack = new Stack();
stack.push(1);
stack.push(2);
console.log("栈的长度:", stack.length());
console.log("栈顶元素:", stack.peek());
console.log("出栈元素:", stack.pop());
console.log("栈顶元素:", stack.peek());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 用两个栈实现队列
入队始终操作 stack1,出队和获取头节点始终操作 stack2。
function StackQueue() {
var stack1 = new Stack(); //负责添加元素
var stack2 = new Stack(); //负责删除元素
// 入队
this.enqueue = function(item) {
stack1.push(item);
};
// 队头元素
this.head = function() {
if (stack1.isEmpty() && stack2.isEmpty()) {
return null;
}
while (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
};
// 队列大小
this.size = function() {
if (!stack1.isEmpty()) {
return stack1.length();
} else {
if (!stack2.isEmpty()) {
return stack2.length();
} else {
return 0;
}
}
};
// 出队
this.dequeue = function() {
if (stack1.isEmpty() && stack2.isEmpty()) {
return null;
}
while (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
};
}
var sQueue = new StackQueue();
sQueue.enqueue(1);
sQueue.enqueue(2);
sQueue.enqueue(3);
console.log(sQueue.head()); // 1
console.log(sQueue.dequeue()); // 1
console.log(sQueue.head()); // 2
console.log(sQueue.size()); // 2
console.log(sQueue.dequeue()); // 2
console.log(sQueue.dequeue()); // 3
console.log(sQueue.size()); // 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 验证回文串
var isPalindrome = function(s) {
s = s.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
const stack = [];
for (let i = 0; i < s.length; i++) {
stack.push(s[i]);
}
let reverse = "";
while (stack.length) {
reverse += stack.pop();
}
return reverse === s;
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 判断括号是否合法 ✅
// 时间和空间复杂度都是 O(n)
var isValid = function(s) {
if (s.length % 2 !== 0) return false;
const stack = [];
for (const c of s) {
switch (c) {
case "(":
case "{":
case "[":
stack.push(c);
break;
case ")":
if (stack.pop() !== "(") return false;
break;
case "}":
if (stack.pop() !== "{") return false;
break;
case "]":
if (stack.pop() !== "[") return false;
}
}
return stack.length === 0;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 时间和空间复杂度都是 O(n)
var isValid = function(s) {
if (s.length % 2 !== 0) return false;
const stack = [];
const bracket = {
"(": ")",
"{": "}",
"[": "]"
};
for (const c of s) {
if (c in bracket) {
stack.push(c);
} else if (stack.length === 0 || bracket[stack.pop()] !== c) {
return false;
}
}
return stack.length === 0;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 最长有效括号
var longestValidParentheses = function(s) {
let res = 0;
const stack = [-1];
for (let i = 0; i < s.length; i++) {
if (s[i] === "(") {
stack.push(i); // 左括号的索引入栈
continue; // 跳过,考察下一个符号
}
stack.pop(); // 遇到右括号,栈顶出栈
if (!stack.length) {
stack.push(i); // 如果栈顶因此为空,说明该更换栈顶索引了
} else {
res = Math.max(res, i - stack[stack.length - 1]); // 比较获取最大的有效长度
}
}
return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 计算后缀表达式(逆波兰表达式)
function calc_exp(exp) {
var stack = new Stack();
for (var i = 0; i < exp.length; i++) {
var item = exp[i];
if (["+", "-", "*", "/"].indexOf(item) >= 0) {
// 从栈顶弹出两个元素
var value1 = stack.pop();
var value2 = stack.pop();
// 拼成表达式
var exp_str = value2 + item + value1; // 第一次弹出的数放在运算符右边,第二次弹出的数放在运算符左边
// 计算并取整
var res = parseInt(eval(exp_str)); // eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码
// 将计算结果压入栈
stack.push(res.toString());
} else {
stack.push(item);
}
}
// 若表达式正确,最终栈里应只有一个元素,这就是表达式的值
return stack.pop();
}
var exp1 = ["4", "13", "5", "/", "+"]; // (4 + (13 / 5)) = 6
// var exp2 = ['10','6','9','3','+','-11','*','/','*','17','+','5','+']; // ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = 22
console.log(calc_exp(exp1));
console.log(calc_exp(exp2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 十进制转二进制
function BinaryConversion(number) {
var stack = new Stack();
var remainder;
var binary = ""; // 存储二进制
while (number > 0) {
remainder = number % 2; // 求模取余
stack.push(remainder);
number = Math.floor(number / 2); // 向下取整
}
while (!stack.isEmpty()) {
binary += stack.pop();
}
return binary;
}
console.log(BinaryConversion(10)); // 1010
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15