【数据结构c++】(学习笔记)队列(循环队列、链式队列、两个栈实现一个队列、两个队列实现一个栈)

学习笔记,部分内容参考网络教学视频,习题来源Leetcode,侵删

队列

先进先出,后进后出

环形队列

依赖数组实现,但必须实现环形

在这里插入图片描述
两根指针,first指向队首,rear指向队尾,rear指向的是最后一个元素的下一个位置,也表示队内元素个数

使用数组实现线型队列,first以前的数组空间全部浪费,导致数组开多大都不够,所以选择环形

  • 初始化:first = rear = 0;
  • 入队:arr[rear] = 10; rear = (rear + 1) % len); // 不是线型的 rear++;
  • 出队:first++;
  • 判满判空:预留一个空位置给rear,否则rearfirst重合时无法分辨是满还是空
    • 空的:first == rear
    • 满的:(rear + 1) / len) == first

代码实现:

#include <iostream>
using namespace std;

class Queue
{
public:
    Queue(int size = 10) : cap_(size),
                           front_(0),
                           back_(0),
                           size_(0)
    {
        pQueue_ = new int[cap_];
    }

    ~Queue()
    {
        delete[] pQueue_;
        pQueue_ = nullptr;
    }

public:
    // 入队
    void push(int val)
    {
        if ((back_ + 1) % cap_ == front_) // 满了扩容
        {
            expand(2 * cap_);
        }

        pQueue_[back_] = val;
        back_ = (back_ + 1) % cap_;
        size_++;
    }

    // 出队
    void pop()
    {
        if (front_ == back_)
        {
            throw "The queue is empty!";
        }
        front_ = (front_ + 1) % cap_;
        size_--;
    }

    // 队首
    int front() const
    {
        if (front_ == back_)
        {
            throw "The queue is empty!";
        }
        return pQueue_[front_];
    }

    // 队尾
    int back() const
    {
        if (front_ == back_)
        {
            throw "The queue is empty!";
        }
        return pQueue_[(back_ - 1 + cap_) % cap_];
    }

    // 队空
    bool empty() const
    {
        return front_ == back_;
    }

    // 队列元素个数
    int size()
    {
        return size_; // O(1)
        /*
        或者 O(n) 遍历
        int size = 0;
        for(int i = front_; i != rear_; i = (i + 1) % cap_)
        {
            size++;
        }
        return size;
        */
    }

private:
    void expand(int size)
    {
        int *p = new int[size];
        // 不能直接操作
        // i遍历新数组,j遍历老数组
        int i = 0;
        int j = front_;
        for (; j != back_; j = (j + 1) % cap_,i++)
        {
            p[i] = pQueue_[j];
        }
        delete[] pQueue_;
        pQueue_ = p;
        // 还有一些属性要修改
        cap_ = size;
        front_ = 0;
        back_ = i;
    }

private:
    int *pQueue_;
    int cap_;   // 空间容量
    int front_; // 队头
    int back_;  // 队尾
    int size_;
};

int main()
{
    try
    {
        // 测试基本功能
        Queue q(3);
        cout << "队列是否为空? " << (q.empty() ? "是" : "否") << endl; // 是

        // 入队测试
        q.push(10);
        q.push(20);
        q.push(30); // 触发扩容到6
        cout << "入队后队列大小: " << q.size() << endl; // 3
        cout << "队首元素: " << q.front() << endl;     // 10
        cout << "队尾元素: " << q.back() << endl;      // 30

        // 出队测试
        q.pop();
        cout << "出队后队首元素: " << q.front() << endl; // 20
        cout << "出队后队尾元素: " << q.back() << endl;  // 30

        // 测试扩容后继续入队
        q.push(40);
        q.push(50);
        q.push(60); // 容量6已满
        q.push(70); // 触发扩容到12
        cout << "扩容后队列大小: " << q.size() << endl; // 6

        // 测试队空异常
        Queue emptyQueue;
        emptyQueue.pop(); // 应抛出异常
    }
    catch (const char* msg)
    {
        cout << "异常: " << msg << endl;
    }

    // 测试边界情况
    try
    {
        Queue q;
        q.push(1);
        cout << "单元素队首: " << q.front() << endl; // 1
        cout << "单元素队尾: " << q.back() << endl;  // 1
        q.pop();
        cout << "队列是否为空? " << (q.empty() ? "是" : "否") << endl; // 是
    }
    catch (const char* msg)
    {
        cout << "异常: " << msg << endl;
    }

    return 0;
}

链式队列

依赖双向循环链表实现

#include <iostream>
using namespace std;

class LinkQueue
{
public:
    LinkQueue() : size_(0)
    {
        head_ = new Node();
        head_->next_ = head_;
        head_->pre_ = head_;
    }

    ~LinkQueue()
    {
        Node *p = head_->next_;
        while (p != head_)
        {
            head_->next_ = p->next_;
            p->next_->pre_ = head_;
            delete p;
            p = head_->next_;
        }
        delete head_;
        head_ = nullptr;
    }

public:
    void push(int val)
    {
        Node *node = new Node(val);
        node->next_ = head_;
        node->pre_ = head_->pre_;
        head_->pre_->next_ = node;
        head_->pre_ = node;
        size_++;
    }

    void pop()
    {
        if (empty()) // 添加边界检查
        {
            throw "The queue is empty!";
        }
        Node *p = head_->next_;
        head_->next_ = p->next_;
        p->next_->pre_ = head_;
        delete p;
        p = head_->next_;
        size_--;
    }

    int front() const
    {
        if (head_->next_ == head_)
        {
            throw "The queue is empty!";
        }
        return head_->next_->data_;
    }

    int back() const
    {
        if (head_->next_ == head_)
        {
            throw "The queue is empty!";
        }
        return head_->pre_->data_;
    }

    bool empty() const
    {
        return head_->next_ == head_;
    }

    int size()
    {
        return size_;
    }

private:
    struct Node
    {
        Node(int data = 0) : data_(data),
                             next_(nullptr),
                             pre_(nullptr)
        {
        }
        int data_;
        Node *next_;
        Node *pre_;
    };

    Node *head_;
    int size_;
};

int main()
{
    try
    {
        // 测试基本功能
        LinkQueue q;
        cout << "队列是否为空? " << (q.empty() ? "是" : "否") << endl; // 是

        // 入队测试
        q.push(10);
        q.push(20);
        q.push(30);                                     // 触发扩容到6
        cout << "入队后队列大小: " << q.size() << endl; // 3
        cout << "队首元素: " << q.front() << endl;      // 10
        cout << "队尾元素: " << q.back() << endl;       // 30

        // 出队测试
        q.pop();
        cout << "出队后队首元素: " << q.front() << endl; // 20
        cout << "出队后队尾元素: " << q.back() << endl;  // 30

        // 测试扩容后继续入队
        q.push(40);
        q.push(50);
        q.push(60);                                     // 容量6已满
        q.push(70);                                     // 触发扩容到12
        cout << "扩容后队列大小: " << q.size() << endl; // 6

        // 测试队空异常
        LinkQueue emptyQueue;
        emptyQueue.pop(); // 应抛出异常
    }
    catch (const char *msg)
    {
        cout << "异常: " << msg << endl;
    }

    // 测试边界情况
    try
    {
        LinkQueue q;
        q.push(1);
        cout << "单元素队首: " << q.front() << endl; // 1
        cout << "单元素队尾: " << q.back() << endl;  // 1
        q.pop();
        cout << "队列是否为空? " << (q.empty() ? "是" : "否") << endl; // 是
    }
    catch (const char *msg)
    {
        cout << "异常: " << msg << endl;
    }

    return 0;
}

用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

示例 1:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示:

  • 1 <= x <= 9
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

进阶:

  • 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。

算法

在这里插入图片描述
较为简单地想,拿出最后一个元素只需要把s1元素入栈到s2s2栈首元素出栈就是队首出栈,剩下的元素再入栈回s1

但是pop,peek(查看队首)操作都会是时间复杂度为O(n),还可以继续优化

s1负责push,如果做poppeek都从s2操作(如果s2是空的,就把s1元素都拿过来),判空empty则需要s1,s2均为空。

代码实现

#include <iostream>
#include <stack>
using namespace std;

class MyQueue
{
public:
    MyQueue()
    {
    }

    void push(int x)
    {
        s1.push(x);
    }

    int pop()
    {
        if (s2.empty())
        {
            while (!s1.empty())
            {
                s2.push(s1.top());
                s1.pop();
            }
        }
        int val = s2.top();
        s2.pop();
        return val;
    }

    int peek()
    {
        if (s2.empty())
        {
            while (!s1.empty())
            {
                s2.push(s1.top());
                s1.pop();
            }
        }
        return s2.top();
    }

    bool empty()
    {
        return s1.empty() && s2.empty();
    }

private:
    stack<int> s1;
    stack<int> s2;
};

用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false

注意:

  • 你只能使用队列的标准操作 —— 也就是 push to backpeek/pop from frontsizeis empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:

  • 1 <= x <= 9
  • 最多调用100pushpoptopempty
  • 每次调用 poptop 都保证栈不为空

**进阶:**你能否仅用一个队列来实现栈。

基础算法

在这里插入图片描述

  1. 两个队列分工
    • 一个主队列 q1 用于存储栈的元素(非空)。
    • 一个辅助队列 q2 始终为空,仅在出栈时临时使用。
  2. 入栈(push
    直接将元素加入主队列 q1(时间复杂度 O (1))。
  3. 出栈(pop
    • q1 中除最后一个元素外的所有元素转移到 q2
    • 弹出 q1 中剩余的最后一个元素(即栈顶元素)。
    • 交换 q1q2 的角色(q2 变为新的主队列)。
      (时间复杂度 O (n),n 为当前元素个数)
  4. 取栈顶(top
    逻辑与 pop 类似,但转移元素后不弹出最后一个元素,而是记录其值后放回(或直接返回队列尾部元素)。
  5. 判空(empty
    主队列 q1 为空时,栈为空。

代码实现

#include <iostream>
#include <queue>
using namespace std;

class MyStack 
{
private:
    queue<int> q1;  // 主队列(存储元素)
    queue<int> q2;  // 辅助队列(临时使用)

public:
    MyStack() {}
    
    void push(int x) 
    {
        q1.push(x);  // 直接加入主队列
    }
    
    int pop() 
    {
        // 将 q1 中前 n-1 个元素转移到 q2
        while (q1.size() > 1) 
        {
            q2.push(q1.front());
            q1.pop();
        }
        // q1 中剩余的最后一个元素是栈顶
        int topVal = q1.front();
        q1.pop();
        // 交换 q1 和 q2,使 q2 恢复为空
        swap(q1, q2);
        return topVal;
    }
    
    int top() 
    {
        // 复用 pop 的逻辑,但保留最后一个元素
        while (q1.size() > 1) 
        {
            q2.push(q1.front());
            q1.pop();
        }
        int topVal = q1.front();  // 记录栈顶值
        // 将最后一个元素也转移到 q2,保持 q1 为空
        q2.push(q1.front());
        q1.pop();
        swap(q1, q2);  // 恢复 q1 为主队列
        return topVal;
    }
    
    bool empty() 
    {
        return q1.empty();  // 主队列空则栈空
    }
};

进阶算法

在这里插入图片描述
只需一个队列即可实现,核心思路是:入栈后将队列中前 n-1 个元素重新入队,使新入栈的元素位于队首(模拟栈顶)。

#include <iostream>
#include <queue>
using namespace std;

class MyStack
{
private:
    queue<int> q;

public:
    MyStack() {}

    void push(int x)
    {
        q.push(x);
        // 将前 n-1 个元素重新入队,使 x 位于队首
        for (int i = 0; i < q.size() - 1; ++i)
        {
            q.push(q.front());
            q.pop();
        }
    }

    int pop()
    {
        int topVal = q.front();
        q.pop();
        return topVal;
    }

    int top()
    {
        return q.front();
    }

    bool empty()
    {
        return q.empty();
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值