栈和队列类似于数组,但在添加和删除元素时更为可控。

1. 栈数据结构

栈是一种遵从后进先出(LIFO)原则的有序集合,两端分别是栈顶和栈底。新添加的或者待删除的元素都保存在栈顶。编译器和内存中保存变量、方法调用等都会用到栈。
image.png

1.1 创建栈

  • push(element(s)):添加一个(或几个)新元素到栈顶。
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false。
  • clear():移除栈里的所有元素。
  • size():返回栈里的元素个数。这个方法和数组的length属性很类似。

    1.1.1 传统方式

    1. function Stack() {
    2. let items = [];
    3. this.push = function (element) {
    4. items.push(element);
    5. };
    6. this.pop = function () {
    7. return items.pop();
    8. };
    9. this.peek = function () {
    10. return items[items.length - 1];
    11. };
    12. this.isEmpty = function () {
    13. return items.length === 0;
    14. };
    15. this.size = function () {
    16. return items.length;
    17. };
    18. this.clear = function () {
    19. items = [];
    20. };
    21. this.print = function () {
    22. console.log(items.toString());
    23. };
    24. }

    1.1.2 ES6 Class

    使用了 WeakMap,键是对象,值是任意数据类型。WeakMap 对比 Map 而言(WeakSet 同理):

  • 没有 entries、keys、values 等迭代器方法;

  • 只能用对象作为键。

这里键是 this,值是数组。从而每个实例都可以根据自己的 this 获取私有的数组池。除非知道别的实例 this,否则也不会取出别的实例中的值(因为没有迭代器方法)。

  1. const Stack = (function () {
  2. const items = new WeakMap();
  3. class Stack {
  4. constructor() {
  5. items.set(this, []);
  6. }
  7. push(element) {
  8. const s = items.get(this);
  9. s.push(element);
  10. }
  11. pop() {
  12. const s = items.get(this);
  13. return s.pop();
  14. }
  15. peek() {
  16. const s = items.get(this);
  17. return s[s.length - 1];
  18. }
  19. isEmpty() {
  20. const s = items.get(this);
  21. return s.length === 0;
  22. }
  23. size() {
  24. const s = items.get(this);
  25. return s.length;
  26. }
  27. clear() {
  28. items.set(this, []);
  29. }
  30. print() {
  31. const s = items.get(this);
  32. console.log(s.toString());
  33. }
  34. }
  35. return Stack;
  36. })();

2. 栈相关问题

据说栈有三个著名的算法场景:

  • 十进制转二进制
  • 平衡圆括号
  • 汉诺塔问题

    2.1 十进制转二进制

    image.png ```javascript /**

    • 十进制数字和2整除,直至结果是0,然后反向输出余数,得到转换后的二进制 */ function convert10To2(decNumber) { let remStack = new Stack(), rem, binaryString = ‘’;

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

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

    return binaryString; }

const test = 15; const result = convert10To2(test); console.log(result); console.log(‘测试转换结果是否正确:’, result === test.toString(2));

  1. <a name="KeIG6"></a>
  2. ## 2.2 任意进制转十进制
  3. ```javascript
  4. /**
  5. * 任意进制转十进制(十六进制及以内)
  6. */
  7. function baseConverter(decNumber, base) {
  8. let remStack = new Stack(),
  9. rem,
  10. baseString = '',
  11. digits = '0123456789ABCDEF';
  12. while (decNumber > 0) {
  13. rem = decNumber % base;
  14. remStack.push(rem);
  15. decNumber = Math.floor(decNumber / base);
  16. }
  17. while (!remStack.isEmpty()) {
  18. baseString += digits[remStack.pop()];
  19. }
  20. return baseString;
  21. }
  22. const test2 = 2501234;
  23. const result2 = baseConverter(test2, 16);
  24. console.log(result2);
  25. console.log('测试转换结果是否正确:', result2 === test2.toString(16).toUpperCase());

2.3 判断括号是否平衡

  1. // https://leetcode.com/problems/valid-parentheses/
  2. /**
  3. * 判断括号是否匹配
  4. *
  5. * @param {string} s
  6. * @return {boolean}
  7. */
  8. var isValid = function (s) {
  9. if (s.length === 0) return true;
  10. const stack = [];
  11. // 遇见左括号就入栈,右括号就弹出栈顶进行匹配
  12. // 平衡多少对儿,就弹出多少个左括号
  13. // 若能全部平衡,则最后栈应为空
  14. const dict = {
  15. ')': '(',
  16. '}': '{',
  17. ']': '['
  18. };
  19. const isLeft = (c) => '({['.includes(c);
  20. if (!isLeft(s[0])) return false;
  21. for (let i = 0; i < s.length; i++) {
  22. const c = s[i];
  23. if (isLeft(c)) {
  24. stack.push(c);
  25. } else {
  26. if (dict[c] !== stack.pop()) {
  27. return false;
  28. }
  29. }
  30. }
  31. return stack.length === 0;
  32. };