数据结构(一)栈

栈是一个后进先出的数据结构

JavaScript中没有栈,但可以用Array实现栈的所有功能

栈常见操作:push、pop、stack[stack. length-1]

1. 定义

后进先出,相当于往容器里放东西

用途:

编程语言的编译器和内存中保存变量、方法调用

浏览器历史记录(浏览器的返回按钮)

函数堆栈调用

……

2. 具体操作

创建

1
2
3
4
5
class Stack {
constructor() {
this.items = [];
}
}

方法

  • push(element(s)):添加一个(或几个)新元素到栈顶

    1
    2
    3
    push(element) {
    this.items.push(element);
    }
  • pop():移除栈顶的元素,同时返回被移除的元素

    1
    2
    3
    pop() {
    return this.items.pop();
    }
  • peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)

    1
    2
    3
    peek() {  //查看栈里最后添加的元素是什么
    return this.items[this.items.length - 1];
    }
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false

    1
    2
    3
    isEmpty() {  //判断内部数组的长度是否为0
    return this.items.length === 0;
    }
  • clear():移除栈里的所有元素

    1
    2
    3
    clear() {  //也可以多次调用pop方法,把数组中的元素全部移除
    this.items = [];
    }
  • size():返回栈里的元素个数(和length 属性类似)

    1
    2
    3
    size() {  //对于集合,最好使用size代替length
    return this.items.length;
    }

3. 使用

1
2
3
4
5
6
7
8
9
10
11
const stack = new Stack();  //创建栈
console.log(stack.isEmpty()); //此时栈为空
stack.push(5); //添加元素到栈顶
console.log(stack.peek()); //5 => 往栈里添加的最后一个元素为5
console.log(stack.size()); //1
console.log(stack.isEmpty()); //false
stack.push(8);
stack.push(11);
stack.pop();
stack.pop(); //调用两次,移除两个元素
console.log(stack.size()); //1

4. 基于JavaScript对象的Stack类

最简单的方式:使用数组来存储其元素

基本结构:

1
2
3
4
5
6
7
class xxx {
constructor() {
//构造
}
//写方法
}
//调用

5. 保护数据结构内部元素

使用 “_” “#” 前缀来声明私有属性,需要编写相应的代码逻辑

使用es6中的Symbol

使用es6中的WeakMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const items = new WeakMap();

class Stack {
construtor() {
items.set(this, []);
}
push(element) {
const s = items.get(this);
s.push(element);
}
pop() {
const s = items.get(this);
const r = s.pop();
return r;
}
}

6. 应用

十进制 —> 二进制

通过二进制的计算方法,可以知道二进制的结果是余数从高位到低位排列的

二进制的计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
decimalToBinary = (decNumber) => {
const remStack = new Stack();
let number = decNumber;
let rem;
let binaryString = '';

while (number > 0) {
rem = Math.floor(number % 2);
remStack.push(rem);
number = Math.floor(number / 2);
}

while (!remStack.isEmpty()) {
binaryString += remStack.pop().toString();
}

return binaryString;
}

其他进制转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function baseConverter(decNumber, base) {  //decNumber转换的数字,base几进制
const remStack = new Stack();
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let number = decNumber;
let rem;
let baseString = '';

if (!(base >= 2 && base <= 36)) {
return '';
}

while (number > 0) {
rem = Math.floor(number % base);
remStack.push(rem); //往stack里添加元素
number = Math.floor(number / base);
}

while (!remStack.isEmpty()) {
baseString += digits[remStack.pop()]; //从堆里面拿出元素
}
return baseString;
}

console.log(baseConverter(1111111, 3));

函数的堆栈调用

1
2
3
4
5
6
7
const func1 = () => {
func2();
}
const func2 = () => {
func3();
}
const func3 = () => {}

我们使用node进行debug:

可以看到,实际上,函数的调用也是满足后进先出的顺序

即,func3()最后进去,最先执行完的是func3()

一道LeetCode题

LeetCode20

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
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
const stack = [];
for(let i = 0; i < s.length; i++) {
const c = s[i];
if(c === '(' || c=== '{' || c === '[') {
stack.push(c);
} else {
const t = stack[stack.length - 1]; //栈顶元素
if(
(t === '(' && c === ')') ||
(t === '{' && c === '}') ||
(t === '[' && c === ']')
) {
stack.pop();
} else {
return false;
}
}
}
return stack.length === 0;
};