数据结构与算法--队列一

542 阅读7分钟

文章简要概述

  • 本文主要进行队列相关的算法题刷题题解记录,带你进一步了解队列相关算法以及如何解。
  • 这篇文章主要介绍leetcode中设计循环队列设计循环双端队列、和设计前中后队列的解题思路。

队列相关概念

队列(queue)是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人。

特点:

  • 队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。
  • 在队尾添加元素,在队头删除元素。

610439-20160130151820974-712348880.png

610439-20160130151830318-592472607.png

610439-20160130152004193-1945216114.png

610439-20160130152048146-364086587.png

与队列相关算法

设计循环队列

设计循环队列--leetcode

题目大意:

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

示例

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3

circularQueue.enQueue(1);  // 返回 true

circularQueue.enQueue(2);  // 返回 true

circularQueue.enQueue(3);  // 返回 true

circularQueue.enQueue(4);  // 返回 false,队列已满

circularQueue.Rear();  // 返回 3

circularQueue.isFull();  // 返回 true

circularQueue.deQueue();  // 返回 true

circularQueue.enQueue(4);  // 返回 true

circularQueue.Rear();  // 返回 4

解题思路:

  • 借助一维数组来模拟循环队列结构,利用数组下标来模拟环。

  • 根据队首位置、队列长度、数组长度,可以计算出队尾位置 tailIndex = (headIndex + L − 1) % max

622_queue_with_array.png

代码:

function MyCircularQueue (k) {
    this.list = new Array(k);
    this.front = 0;
    this.cont = 0;
    this.max = k;
    return this;
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularQueue.prototype.enQueue = function(value) {
   if (this.isFull()) {
       return false
   }
   this.list[(this.front + this.cont) % this.max] = value;
   this.cont++;
   return true;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.deQueue = function() {
    if (this.isEmpty()) {
      return false;
    }
    this.list[this.front] = null;
    this.front = (this.front + 1) % this.max;
    this.cont--;
    return true
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Front = function() {
   if (this.isEmpty()) {
       return -1;
   }
   return this.list[this.front];
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Rear = function() {
    if (this.isEmpty()) {
      return -1;
    }
    const rear = (this.front + this.cont -1) % this.max;
    return this.list[rear];
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isEmpty = function() {
   return this.cont === 0;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isFull = function() {
   return this.cont === this.max;
};

设计循环双端队列

设计循环双端队列--leetcode

题目大意:

设计实现双端队列。
你的实现需要支持以下操作:

MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。

示例:

MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3

circularDeque.insertLast(1); // 返回 true

circularDeque.insertLast(2); // 返回 true

circularDeque.insertFront(3); // 返回 true

circularDeque.insertFront(4); // 已经满了,返回 false

circularDeque.getRear(); // 返回 2

circularDeque.isFull(); // 返回 true

circularDeque.deleteLast(); // 返回 true

circularDeque.insertFront(4); // 返回 true

circularDeque.getFront(); // 返回 4

解题思路:

  • 这道题和上一题的整体思路很像
  • front:指向队列头部第 1 个有效数据的位置
  • rear:指向队列尾部(即最后 11 个有效数据)的 下一个位置,即下一个从队尾入队元素的位置
  • 判别队列为空的条件是:front == rear;
  • 判别队列为满的条件是:(rear + 1) % max == front;
  • 指针后移的时候,下标 + 1,要取模;

代码:

   class MyCircularDeque {
        constructor(k){
            this.max = k+1;
            this.list = new Array(this.max);
            this.front = 0; // 头部指向第 1 个存放元素的位置 //插入时,先减,再赋值 //删除时,索引 +1(注意取模)
            this.rear= 0;   // 尾部指向下一个插入元素的位置 // 插入时,先赋值,再加 // 删除时,索引 -1(注意取模)
        }
        insertFront(value){
            if(this.isFull()){ return false }
            this.front = (this.front-1+this.max)%this.max;
            this.list[this.front] = value;
            return true;
        }
        insertLast(value){
            if(this.isFull()){ return false }
            this.list[this.rear] = value;
            this.rear = (this.rear+1) % this.max;
            return true;
        }
        deleteFront(){
            if(this.isEmpty()){ return false }
            this.front = (this.front+1) % this.max;
            return true;
        }
        deleteLast(){
            if(this.isEmpty()) { return false};
            this.rear = (this.rear-1+this.max) % this.max;
            return true;
        }
        getFront(){
            if(this.isEmpty()){ return -1};
            return this.list[this.front];
        }
        getRear(){
            if(this.isEmpty()){ return -1};
            // 当 rear 为 0 时防止数组越界, rear指向的是下一个插入的元素的位置,元素为空,需要-1;
            return this.list[(this.rear-1+this.max) % this.max];
        }
        isEmpty(){
            return this.front == this.rear;
        }
        isFull(){
            return (this.rear+1) % this.max == this.front;
        }

    }

设计前中后队列

设计前中后队列--leetcode

题目大意:

请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。

请你完成 FrontMiddleBack 类:

FrontMiddleBack() 初始化队列。
void pushFront(int val) 将 val 添加到队列的 最前面 。
void pushMiddle(int val) 将 val 添加到队列的 正中间 。
void pushBack(int val) 将 val 添加到队里的 最后面 。
int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:

将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。
从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素,返回 3 ,数组变为 [1, 2, 4, 5, 6] 。

示例:

输入: ["FrontMiddleBackQueue", "pushFront", "pushBack", "pushMiddle", "pushMiddle", "popFront", "popMiddle", "popMiddle", "popBack", "popFront"]

[[], [1], [2], [3], [4], [], [], [], [], []]

输出: [null, null, null, null, null, 1, 3, 4, 2, -1]

解释: FrontMiddleBackQueue q = new FrontMiddleBackQueue();

q.pushFront(1); // [1]

q.pushBack(2); // [1, 2]

q.pushMiddle(3); // [1, 3, 2]

q.pushMiddle(4); // [1, 4, 3, 2]

q.popFront(); // 返回 1 -> [4, 3, 2]

q.popMiddle(); // 返回 3 -> [4, 2]

q.popMiddle(); // 返回 4 -> [2]

q.popBack(); // 返回 2 -> []

q.popFront(); // 返回 -1 -> [] (队列为空)

解题思路:

  • 这道题采用数组的方式也可以实现,但本题我们采用链表的方式模拟。
  • 构建一个链表的方法。
  • 建立虚拟的头节点作为我们队列的队首的前一项。
  • 队首插入元素就是在虚拟头节点的后一位插入。
  • 队中插入元素,我们可以用快慢指针,快指针是慢指针两倍速度,但快指针到链表尾部是,慢指针刚好到要插入的位置前一项。
  • 队尾插入,遍历链表到尾部,插入即可
  • 弹出元素的思路和插入的思路相差不大。

代码:

   function NodeList (val, next) {
    this.val = val;
    this.next = next;
}
var FrontMiddleBackQueue = function() {
    this.head = new NodeList(-1);
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushFront = function(val) {
   this.head.next = new NodeList(val, this.head.next);
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
   let slot = this.head;
   let fast = this.head.next;
   while(fast?.next) {
       slot = slot.next;
       fast = fast.next.next;
   }
   const middle = slot.next;
   slot.next = new NodeList(val, middle);
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function(val) {
  let p = this.head;
  while(p.next) {
      p = p.next;
  }
  p.next = new NodeList(val, null);
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function() {
    const front = this.head.next;
    if (!front) return -1;
    this.head.next = front.next;
    return front.val;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function() {
   let slot = this.head;
   let fast = this.head.next;
   if(fast === null) return -1
   while(fast?.next) {
       fast = fast.next.next;
       if (fast) {
         slot = slot.next;
       }
   }
   const middle = slot.next;
   slot.next = middle.next;
   return middle.val;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function() {
   let back = this.head;
   if (!back.next) return -1;
   while(back?.next?.next) {
       back = back.next;
   }
   const val = back.next.val;
   back.next = null;
   return val;
};

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是队列部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

数据结构与算法-链表一

数据结构与算法-链表二

数据结构与算法-链表三

如果文章对你有帮助,欢迎点赞,关注!

文中图片资源来源于leetcode