JavaScript数据结构和算法(二):栈和队列

210 阅读3分钟

一、栈(LIFO)

1、基于数组的栈

  • 创建一个类来表示栈

    class Stack {
      constructor() {
        this.items = [];
      }
    }
    
  • 向栈添加元素

    push(element) {
      this.items.push(element);
    }
    
  • 从栈移除元素

    pop() {
      return this.items.pop();
    }
    
  • 查看栈顶元素

    peek() {
      return this.items[this.items.length - 1]
    }
    
  • 检查栈是否为空

    isEmpty() {
      return this.items.length === 0
    }
    
  • 清空栈元素

    clear() {
      this.items = [] ;
    }
    

2、基于JavaScript对象的Stack类

  • 声明Stack类

    class Stack {
      constructor() {
        this.count = 0;
        this.items = {};
      }
    }
    
  • 向栈添加元素

    push(element) {
      this.items[this.count] = element;
      this.count++; 
    }
    

    使用count变量作为items对象的键名,插入的元素则是它的值。

  • 验证一个栈是否为空和它的大小

    isEmpty() {
      return this.count === 0;
    }
    ​
    size() {
      return this.count;
    }
    
  • 从栈中弹出元素

    pop() {
      if(this.isEmpty()) {
        return undefined;
      }
      this.count--;
      const result = this.items[this.count];
      delete this.items[this.count];
      return result;
    }
    
  • 查看栈顶的值和将栈清空

    peek() {
      if(this.isEmpty()) {
        return undefined;
      }
      return this.items[this.count - 1];
    }
    ​
    clear() {
      this.items = {};
      this.count = 0;
    }
    
  • 保护数据结构内部元素

    在创建别的开发者也可以使用的数据结构或对象时,要保护内部元素,只有我们暴露的方法才能修改内部结构。

    • 用ES2015的限定作用域Symbol实现类

      ES2015新增一种叫做Symbol的基本类型,它是不可变的,可以用作对象的属性。

      const _items = Symbol('stackItems');
      class Stack {
        constructor () {
          this[_items] = [];
        }
      }
      

      在上面的代码中,我们声明Symbol类型的变量items,在类的constructor函数中初始化它的值。使用this[_items]访问items。

    • 用ES2015的WeakMap实现类

      WeakMap可以确保属性是私有的,存储键值对,其中键是对象,值可以使任意数据类型。

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

3、栈的应用

  • 函数调用堆栈

  • LeetCode20:有效的括号

    • 要求

      给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

      有效字符串需满足:

      左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

    • 示例

      输入:s = "()[]{}"
      输出:true
      ​
      输入:s = "([)]"
      输出:false
      
    • 解法

      /**
         * @param {string} s
         * @return {boolean}
         */
        var isValid = (s) => {
          if (s.length % 2 === 1) {
            return false;
          }
          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;
        };
      

2、队列(FIFO)

1、队列数据结构

  • 创建队列

    class Queue {
      constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.items = {};
      }
    }
    
  • 向队列添加元素(与Stack类中push方法相同)

    enqueue(element) {
      this.items[this.count] = element;
      this.count ++;
    }
    
  • 检查队列是否为空和获取它的长度

    isEmpty() {
      return this.count - this.lowestCount === 0; // return this.size() === 0
    }
    ​
    size() {
      return this.count - this.lowestCount;
    }
    
  • 从队列中移除元素

    dequeue() {
      if(this.isEmpty()) {
        return undefined;
      }
      const result = this.items[this.lowestCount];
      delete this.items[this.lowestCount];
      this.lowestCount++;
      return result;
    }
    
  • 清空队列

    clear() {
      this.items = {};
      this.count = 0;
      this.lowestCount = 0;
    }
    

2、双端队列

  • 创建Deque类

    class Deque {
      constructor() {
        this.count = 0;
        this.lowestCount = 0;
        this.items = {};
      }
    }
    
  • 向双端队列的前端添加元素

    addFront(element) {
      if(this.isEmpty()) { // 双端队列是空的情况
        this.addBack(element);
      } else if(this.lowestCount > 0) { // 一个元素已经被从双端队列的前端移除
        this.lowestCount--;
        this.items[this.lowestCount] = element;
      } else { // lowestCount为0,将所有元素后移一位空出第一个位置
        for(let i = this.count; i > 0; i--) {
          this.items[i] = this.items[i-1];
        }
        this.count++;
        this.lowestCount = 0;
        this.items[0] = element;
      }
    }
    

3、队列的应用

  • JavaScript中的任务队列

  • LeetCode933:最近的请求次数

    • 要求

      写一个 RecentCounter 类来计算特定时间范围内最近的请求。

      请你实现 RecentCounter 类:

      RecentCounter() 初始化计数器,请求数为 0 。 int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。 保证 每次对 ping 的调用都使用比之前更大的 t 值。

    • 示例

      输入:
      ["RecentCounter", "ping", "ping", "ping", "ping"]
      [[], [1], [100], [3001], [3002]]
      输出:
      [null, 1, 2, 3, 3]
      ​
      解释:
      RecentCounter recentCounter = new RecentCounter();
      recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
      recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
      recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
      recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
      
    • 解法

      var RecentCounter = function () {
          this.q = [];
        };
      ​
        /**
         * @param {number} t
         * @return {number}
         */
        RecentCounter.prototype.ping = function (t) {
          this.q.push(t);
          while (this.q[0] < t - 3000) {
            this.q.shift();
          }
          return this.q.length;
        };
      ​
        /**
         * Your RecentCounter object will be instantiated and called as such:
         * var obj = new RecentCounter()
         * var param_1 = obj.ping(t)
         */