A. 参考文档

1、教程类

2、应用类

B. 正文

1、概述

队列的主要两个操作是 enqueue(入队) 和 dequeue(出队):

入队和出队

栈的主要两个操作是 push(入栈) 和 pop(出栈):

入栈和出栈

因为这两个库底层都是基于链表完成(重操作、轻查询),所以复杂度和链表是一样的。

队列比较常用的是广度优先遍历,在树中是层序遍历,在图中是无权图的最短路径;
能帮助你实现深度优先遍历等;

2、栈的应用

在 JS 中,队列和数组很相似,所以平时使用队列的场景会比较多;而对于栈这种数据结构接触的比较少,因此下面罗列的应用偏栈的应用比较多;

以下示例可以到 https://runkit.com/boycgit/ss-stack 查看具体的运行情况

2.1、十进制转二进制

来自文章 数据结构(三)之栈结构

要把十进制转化成二进制,我们可以将该十进制数字和2整除(二进制是满二进一),直到结果是0为止。举个例子,把十进制的数字10转化成二进制的数字,过程大概是这样:

二进制

var Stack = require("ss-stack");
// 封装十进制转二进制的函数
function dec2bin(decNumer) {
    // 定义变量
    var stack = new Stack()
    var remainder;

    // 循环除法
    while (decNumer > 0) {
        remainder = decNumer % 2;
        decNumer = Math.floor(decNumer / 2);
        stack.push(remainder);
    }

    // 将数据取出
    var binayriStrng = ""
    while (!stack.isEmpty()) {
        binayriStrng += stack.pop();
    }
    return binayriStrng;
}

console.log(dec2bin(10));
console.log(dec2bin(233));
console.log(dec2bin(1000));

2.2、括号匹配问题

示例来自 JS括号匹配问题

var Stack = require("ss-stack");
function validBraces(braces){
  let leftBraReg = /[\(\{\[]/, 
    // 栈
  rightBracket
  braces = braces.split('')
  let stack = new Stack();
  for(let bracket of braces) {
    if(leftBraReg.test(bracket)) {
      stack.push(bracket)
    }
    else {
      switch (bracket) {
          case ')':
          rightBracket = stack.pop()
          if(rightBracket !=='(') {
              return false
          }
          break
        case ']':
          rightBracket = stack.pop()
          if(rightBracket !=='[') {
              return false
          }
          break
        case '}':
          rightBracket = stack.pop()
          if(rightBracket !=='{') {
              return false
          }
          break
      }
    }
  }
  return stack.length === 0 ? true : false
}

validBraces("(){}[]")     // true 
validBraces("(}")      // false 
validBraces("[(])")    // false 
validBraces("([{}])")     // true 

3、汉诺塔求解

整个算法的思路是:

  • 将 a 柱子上的 n-1 个盘子暂时移到 b 柱子上
  • a 柱子只剩下最大的盘子,把它移到目标柱子 c 上
  • 最后再将 b 柱子上的 n-1 个盘子移到目标柱子 c 上

汉诺塔的递归法优雅而精妙,伪代码如下:

//将n个盘子按规则从a柱子,移动到c柱子
hanoi( n, a, b, c )
{
    if( n > 0 )
    {
        hanoi(n-1,a,c,b);
        //将a柱子的最上面的盘子移到c柱子
        c.push( a.pop() );
        hanoi(n-1,b,a,c);
    }
}

代码来自 JavaScript数据结构与算法——栈及其应用

var Stack = require("ss-stack");
var towerOfHanoi = function(n, from, help, to) {
  if (n > 0) {
    towerOfHanoi(n-1, from, to, help);
    to.push(from.pop());
    console.log('----------------');
    console.log(`from: ${from.stack.toArray()}`);
    console.log(`help: ${help.stack.toArray()}`);
    console.log(`to: ${to.stack.toArray()}`);
    towerOfHanoi(n-1, help, from, to);
  }
}

var a = new Stack(),
    b = new Stack(),
    c = new Stack(),
    n = 4;

for(let i = n; i > 0; i--) {
  a.push(i);
}
// test
towerOfHanoi(a.length, a, b, c);

4、调度场算法(中缀表达式转后缀表达式)

代码来自 算法 - 调度场算法(Shunting Yard Algorithm)

/* ----------------------------------------------------
    调度场算法,将中缀转换过程后缀表达式
----------------------------------------------------- */
var Stack = require("ss-stack");

// 操作符优先级列表
var PRI_LEFT = 99;
var PRI_RIGHT = 100;
var PRI_TOKEN_MAP = {
    '+': 1,
    '-': 1,
    '*': 2,
    '/': 2,
    '(': PRI_LEFT,
    ')': PRI_RIGHT,
};

// 将中序转换成
function infixToPostfix(expression){
    expression = expression.replace(/\s*/g,""); // 去除所有空白字符
    var opStack = new Stack();
    var str = '';
    
    
    // 符号栈操作
    function calcToken(){
        str += opStack.pop();
    }
    
    for(let token of expression){
        var tokenPri = PRI_TOKEN_MAP[token] || 0;
        var peekTokenPri = opStack.peek && PRI_TOKEN_MAP[opStack.peek] || 0;   
        
        
        // console.log(opStack.stack.toArray());
        // 首先判断是否操作符
        if(tokenPri){
        
            // 情况 1: 栈顶不是左括号;入栈符号不是右括号
            // 循环判断当前栈顶符号,且优先级高于或等于将要入栈的元素,且栈顶不是左括号
            while(opStack.peek && PRI_TOKEN_MAP[opStack.peek] !== PRI_LEFT && tokenPri !== PRI_RIGHT && peekTokenPri >= tokenPri) {
                calcToken();
            }
            
            // 情况2:右括号准备入栈,
            if(tokenPri === PRI_RIGHT){
                // 将符号栈中元素依次弹出,则需要弹栈一直到左括号
                while(PRI_TOKEN_MAP[opStack.peek] !== PRI_LEFT){
                    calcToken();
                }
                opStack.pop(); // 将左括号弹出,不放在数字栈里
            } else {
                // 将当前符号元素入栈
                opStack.push(token);
            }   
        } else { // 不是操作符就直接拼接在字符串上
            str += token;
        }
    }
    
    // 将符号栈中剩余的元素依次弹出
    while(opStack.peek){
        calcToken();
    }
    return str;
}

console.log(infixToPostfix('1+2*3+4')); // "123*+4+"
console.log(infixToPostfix('1 + 2 *  (4 + 5 - 6) ')); // "1 2 4 5 + 6 - * +"
console.log(infixToPostfix('1 + 2 * (3 + (4 + 5 - 6) * 2)')); // "1 2 3 4 5 + 6 - 2 * + * +"
console.log(infixToPostfix('a + b * c + ( d * e + f ) * g')); // "a b c * + d e * f  + g * +"