TypeScript实现队列数据结构

75 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 12 月更文挑战」的第 3 天,点击查看活动详情

最近在学习 Javascript 数据结构与算法相关知识,数据结构与算法对程序员来说就像是内功心法,只有不断修炼自己的内功,才能从容不迫的应对市场上各种让人应接不暇的框架,达到以不变应万变。学习数据结构与算法的过程中不仅要能看懂更要多写多练,今天就来手写下队列数据结构。

队列是遵循先进先出原则的一组有序的项,队列在尾部添加新元素,并从顶部移除元素。新添加的元素必须排在队列的队尾

手写队列

class Queue<T> {
  // 队头计数 用于追踪第一个元素
  private lowestCount: number;
  // 队尾计数 用于控制队列的大小
  private count: number;
  private items: Record<number, T>;

  constructor() {
    this.lowestCount = 0;
    this.count = 0;
    this.items = {};
  }
  // 入队
  enqueue(element: T) {
    // 添加到队尾
    this.items[this.count] = element;
    this.count++;
  }
  // 出队
  dequeue() {
    if (this.isEmpty()) {
      return undefined;
    }
    const result = this.items[this.lowestCount];
    delete this.items[this.lowestCount];
    this.lowestCount++;
    return result;
  }
  // 查看对头元素
  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items[this.lowestCount];
  }
  isEmpty() {
    // 队尾计数和队头计数相等时
    return this.count === this.lowestCount;
  }
  clear() {
    this.items = {};
    this.count = 0;
    this.lowestCount = 0;
  }
  size() {
    // 队尾计数减队头计数
    return this.count - this.lowestCount;
  }
  toString() {
    if (this.isEmpty()) {
      return "";
    }
    let objString = `${this.items[this.lowestCount]}`;
    for (let i = this.lowestCount + 1; i < this.count; i++) {
      objString = `${objString},${this.items[i]}`;
    }
    return objString;
  }
}

Queue 类和前一篇写的栈 Stack 类非常类似。主要的区别在于 dequeue 方法和 peek 方法,这是由于先进先出和后进先出原则的不同所造成的。

双端队列

双端队列(double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。 它同时遵守先进先出后进先出原则,可以说它是把队列和栈相结合的一种数据结构。

class Deque<T> {
  // 队头计数 用于追踪第一个元素
  private lowestCount: number;
  // 队尾计数 用于控制队列的大小
  private count: number;
  private items: Record<number, T>;

  constructor() {
    this.count = 0;
    this.lowestCount = 0;
    this.items = {};
  }
  // 添加到队头
  addFront(element: T) {
    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.items[0] = element;
    }
  }
  // 添加到队尾
  addBack(element: T) {
    this.items[this.count] = element;
    this.count++;
  }
  // 从队头移除
  removeFront() {
    if (this.isEmpty()) {
      return undefined;
    }
    const result = this.items[this.lowestCount];
    delete this.items[this.lowestCount];
    this.lowestCount++;
    return result;
  }
  // 从队尾移除
  removeBack() {
    if (this.isEmpty()) {
      return undefined;
    }
    this.count--;
    const result = this.items[this.count];
    delete this.items[this.count];
    return result;
  }
  // 获取队头元素
  peekFront() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items[this.lowestCount];
  }
  // 获取队尾元素
  peekBack() {
    if (this.isEmpty()) {
      return undefined;
    }
    // 队尾下标为this.count - 1
    return this.items[this.count - 1];
  }

  isEmpty() {
    return this.size() === 0;
  }
  clear() {
    this.items = {};
    this.count = 0;
    this.lowestCount = 0;
  }
  size() {
    return this.count - this.lowestCount;
  }
  toString() {
    if (this.isEmpty()) {
      return "";
    }
    let objString = `${this.items[this.lowestCount]}`;
    for (let i = this.lowestCount + 1; i < this.count; i++) {
      objString = `${objString},${this.items[i]}`;
    }
    return objString;
  }
}

使用场景

需要先进先出的场景 js 中任务队列
leetcode 练习题:933

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

var RecentCounter = function() {
    this.queue = [];
};

/** 
 * @param {number} t
 * @return {number}
 */
RecentCounter.prototype.ping = function(t) {
    this.queue.push(t);
    while (this.queue[0] < t - 3000) {
        this.queue.shift();
    }
    return this.queue.length;
};

解题思路
我们可以用一个队列维护发生请求的时间,当在时间t收到请求时,将时间t入队。

由于每次收到的请求的时间都比之前的大,因此从队首到队尾的时间值是单调递增的。 当在时间 t 收到请求时,为了求出 [t-3000,t] 内发生的请求数,我们可以不断从队首弹出早于 t−3000 的时间。循环结束后队列的长度就是 [t-3000,t]内发生的请求数。