【数据结构整理】队列和栈

队列和栈


·队列

一. 队列的实现

(这里只谈论循环队列, 普通队列空间浪费太严重)

  1. 建议使用类来封装,因为stl中的queue和stack也都是封装好的。

  2. 循环队列反正是使用vector来实现的,毕竟能实现随机访问

  3. 头指针尾指针(用来指向最后一个元素的后一个位置),还有一个表示数据的数目。(由于空元素的存在,数组实际的大小为该元素值 + 1——所有操作都要考虑这一点)

  4. 实现的几个方法

    • 判断队空,队满。

    • 出队,入队。

    • 取队首,队尾元素。

二. 队列的应用

队列似乎没什么用,只有在BFS的时候才可以整。。不过哪怕这样他也很有用了。

三. 模拟队列

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

template<typename T> 
class MyCircularQueue {
 private:
  size_t head_ = 0;
  size_t rear_ = 0;
  vector<T> data_;
  int size_;

 public:
  // Rule of Three
  // 虽然我们想开辟一个长为size的vector,但事实上由于空元素的存在,要开辟为size_ + 1个
  // 注意template的使用
  MyCircularQueue<T>(int size) : size_(size) { data_.resize(size_ + 1); } // Cntr, 对vector进行resize是value-initialize
  MyCircularQueue<T>(const MyCircularQueue<T> &) = default;
  ~MyCircularQueue<T> () = default;

  // 下面只进行6个操作:入队,出队,判断队空,队满,获得队首,队尾元素
  // 判断队空
  bool IsEmpty() { return (head_ == rear_); }
  // 判断队满
  bool IsFull() { return (rear_ + 1) % (size_ + 1) == head_; }
  // 入队
  bool EnQueue(const T &x) {
    if (IsFull()) {
      return false;
    }
    data_[rear_] = x;
    rear_ = (rear_ + 1) % (size_ + 1);
    return true;
  }
  // 出队
  bool DeQueue() {
    if (IsEmpty()) {
      return false;
    }
    head_ = (head_ + 1) % (size_ + 1);
    return true;    // 别忘了返回true
  }
  // 获得队尾元素
  T &GetRear() { return data_[(rear_ - 1) % (size_ + 1)]; } // rear_始终指向最后一个元素的后一个位置
  // 获得队首元素
  T &GetFront() { return data_[head_]; }
};

int main() {
  MyCircularQueue<int> *my_queue = new MyCircularQueue<int>(3);
  for (int i = 1; i <= 3; ++i) {
    my_queue->EnQueue(i);
  }
  // 这里的boolalpha是用来控制把布尔型变量输出成false和true的
  cout << boolalpha << my_queue->EnQueue(4) << endl;    // 这里应该输出false
  cout << my_queue->GetRear() << endl;    // 应该输出3
  my_queue->DeQueue();
  my_queue->EnQueue(4);
  cout << my_queue->GetRear() << endl;    // 应该输出4
  return 0;
}

 

·栈

一. 栈的实现

栈的实现特别简单,因为出栈入栈都是在尾部进行操作的。

而这一点和动态数组(指vector)如出一辙:push_back, pop_back。

stl为了实现算法的高效性甚至都没有给vector提供front方法。这点也和stack不谋而合。

栈只要实现四个方法就好了:

  • 判断栈是否为空

  • 入栈,出栈

  • 取栈顶元素

二. 栈的用途

栈的用处似乎不这么大,因为我们随时可以编写递归调用OS的call stack。——当然要考虑溢出。

栈的用处似乎也特别大,因为有很多题目我们第一眼看就会想到栈。这是哪些题目呢?

比如说:从前往后遍历,当遇到某一个特殊元素时,处理前面的元素

在栈底的元素,优先级都老低了;越往栈顶优先级越高,碰到出栈信号立刻出栈。

这些问题可以查看我的leetcode博客。

三. 模拟栈

// 动态数组和栈完美契合
// 你要知道vector是完全没有在数组最前面操作的方法的
// 但是其pop_back和push_back简直完美
#include<iostream>
#include<vector>
using namespace std;

template<typename T>
class Stack {
 private:
  // 惭愧啊!惭愧!说到动态数组,我的第一想法竟然是new
  // stack的实现确实比队列简单多了,因为只要一个vector即可
  // 另外,如果有top方法,pop就不需要返回栈顶元素了(各司其职嘛)
  vector<T> data_;
 public:
  // 只要实现最简单的四个方法即可
  // 出栈入栈
  void Push(T &x) { data_.push_back(x); }
  bool Pop() {
    if (data_.empty()) {
      return false;
    }
    data_.pop_back();
    return true;
  }
  // 判断栈是否为空
  bool IsEmpty() { return data_.empty(); }
  // 获取栈顶元素
  // (像这种函数是无法判断队空的,除非使用特殊的约定;因为只能返回一个类型)
  T &Top() { return data_.back(); }   // 我曹还真的有back方法啊
};

int main() {
  Stack<int> my_stack;
  for (int i = 1; i <= 4; ++i) {
    my_stack.Push(i);
  }
  cout << boolalpha << my_stack.IsEmpty() << endl;
  my_stack.Pop();
  cout << "栈顶元素:" << my_stack.Top() << endl;
  return 0;
}

 

· 其他

  1. 你要知道栈和队列都是封装好了的,也就是说,你只能通过那几个方法进行栈和队列的操作。也就是说,你不能用for遍历栈和队列,包括resize等许多algorithm都不能使用。

  2. 遍历和入栈的关系:以前的我总是隐隐约约地感觉遍历和栈是一对矛盾。现在我有点弄懂了。通过遍历,可以使更好的元素入栈。——啥意思?比如解析一个算数表达式字符串,碰到数字时,我总要把整个数字(而不是单个字符)入栈。如果抛开遍历,还是采用栈来存储该字符串中的字符。那会特别麻烦(因为要不断地反转)

  3. 关于栈和队列的数量:没有什么是一个栈或者队列解决不了的;如果有,那就两个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值