栈定义
- 后进先出 (Last In First Out)
- 一种限制访问端口的线性表
- 主要操作
- 进栈 (push) 出栈 (pop)
- 应用
- 表达式求值
- 消除递归
- 深度优先搜索
栈的抽象数据类型
template <class T>
class Stack {
public: // 栈的运算集
void clear(); // 变为空栈
bool push(const T item);
// item入栈,成功返回真,否则假
bool pop(T& item); // 返回栈顶内容并弹出,成功返回真,否则假
bool top(T& item); // 返回栈顶但不弹出,成功返回真,否则假
bool isEmpty(); // 若栈已空返回真
bool isFull(); // 若栈已满返回真
};
复制代码
栈主要有两种方式实现
- 顺序栈,线性表的简化版,由于有大小,存在上溢和下溢问题
- 链式栈,采用单链表,从顶端向下链接
顺序栈模板
Link(const T info, Link* nextValue) {// 具有两个参数的Link构造函数
data = info;
next = nextValue;
}
template <class T> class arrStack : public Stack <T> {
private: // 栈的顺序存储
int mSize; // 栈中最多可存放的元素个数
int top; // 栈顶位置,应小于mSize
T *st; // 存放栈元素的数组
public: // 栈的运算的顺序实现
arrStack(int size) { // 创建一个给定长度的顺序栈实例
mSize = size; top = -1; st = new T[mSize];
}
arrStack() { // 创建一个顺序栈的实例
top = -1;
}
~arrStack() { delete [] st; }
void clear() { top = -1; } // 清空栈
}
复制代码
顺序栈和链式栈的比较
-
时间效率
- 所有操作都只需常数时间
- 顺序栈和链式栈在时间效率上难分伯仲
-
空间效率
- 顺序栈须说明一个固定的长度
- 链式栈的长度可变,但增加结构性开销
-
实际应用中,顺序栈比链式栈用得更广泛
- 顺序栈容易根据栈顶位置,进行相对位移,快速定位并读取栈的内部元素
- 顺序栈读取内部元素的时间为O(1),而链式栈则需要沿着指针链游走,显然慢些,读取第?个元素需要 时间为?(?)
-
一般来说,栈不允许“读取内部元素”,只能在栈顶操作
思考问题:
top函数表示取栈顶元素,将结果返回给用户,pop函数表示将栈顶元素弹出(如果栈不空) ,pop函数仅仅是一个操作,并不将结果返回。
– pointer = aStack.ptop()? 可不可行
STL为什么这两个操作分开? 为什么不提供 ptop?
主要是有两方面原因,参考: 1.安全原因,肯能导致悬浮指针 2.考虑到效率,若是ptop(),则必须返回值而不是引用(因为原数据空间已释放),所以效率会变低。
队列的定义
- 先进先出 (First In First Out)
- 限制访问点的线性表
- 按照到达的顺序来释放元素
- 所有的插入在表的一端进行,所有的删除都在表 的另一端进行
- 限制访问点的线性表
- 主要元素
- 队头 (front) – 队尾 (rear)
队列的主要操作
- 入队(enQueue)
- 出队(deQueue)
- 取首元素getFront
- 判断是否为空isEmpty
复制代码
队列的抽象数据类型
template <class T> class Queue { public: // 队列的运算集
void clear(); // 变为空队列
bool enQueue(const T item);// 将item插入队尾,成功则返回真,否则返回假
bool deQueue(T & item) ;// 返回队头元素并将其从队列中删除,成功则返回真 bool getFront(T & item);
bool isEmpty(); //
bool isFull(); //
};
复制代码
队列的实现方式
- 顺序队列
- 关键在于防止假溢出
- 链式队列
- 用单链表方式存储,队列中每个元素对于链 表中的一个结点
顺序队列的类定义
class arrQueue: public Queue<T> {
private:
int mSize;// 存放队列的数组的大小
int front;// 表示队头所在位置的下标
int rear;// 表示队尾所在位置的下标
T * qu;// 存放类型为T的队列元素的数组 // 队列的运算集
public:
arrQueue(int size);// 创建队列的实例
~arrQueue();// 消除该实例,并释放其空间
}
复制代码
链式队列的类定义
template <class T>
class lnkQueue: public Queue<T> {
private:
int size; // 队列中当前元素的个数
Link<T>* front;// 表示队头的指针
Link<T>* rear;// 表示队尾的指针
public:// 队列的运算集
lnkQueue(int size);// 创建队列的实例
~lnkQueue();// 消除该实例,并释放其空间
}
复制代码
顺序队列与链式队列的比较
- 顺序队列
- 固定的存储空间
- 链式队列
- 可以满足大小无法估计的情况
都不允许访问队列内部元素
队列的应用
- 只要满足先来先服务特性的应用均可采用队列 作为其数据组织方式或中间数据结构
- 调度或缓冲
- 消息缓冲器
- 邮件缓冲器
- 计算机硬设备之间的通信也需要队列作为数据缓冲
- 操作系统的资源管理
- 宽度优先搜索
问题:
-
只用front rear,长度为n的队列,最大可容纳多少元素?
- 最大容纳n-1个。因为当队列空时front=rear,当容纳n个元素时也有front=rear。因此当容纳n个元素时无法判断队列的空与满,所以最大容纳n-1个元素。
-
若采用虚指方法实现队尾指针(rear指向队尾元素后一个元素,和实指相比后移一位),在具体实现上有何异同?哪一种更好?
- 具体实现差别在于一个是判断空与满的不同,另一个是插入新节点与rear后移语句顺序的不同。实指针先后移,然后添加新节点,虚指针反之。实指针的判断空与满的判断分别为((rear+1)%n==front)与((rear+2)%n==front),虚指针的判断分别为(rear==front)与((rear+1)%n==front)。两者相比较之下,一方面虚指针的代码更为简洁,另一方面,少一个模运算,能够节省算法时间。(模运算耗时较大),但是就理解上,实指针更为直观。