概述
栈是一种线性序列结构,它的特殊之处在于,栈对于其中的元素的访问做了限制,只能从序列的某一端进行读写操作。我们知道,看过我之前文章的读者应该知道,对于向量或者是列表,我们可以访问其中的任意一个元素。但是对于栈,我们只能访问位于栈的某一特定端的元素。而禁止操作的另一端,我们称之为盲端。
既然栈是一种数据结构,那么它必然也有自己的操作接口,栈所支持的操作接口有以下几种:
操作接口 | 功能 |
---|---|
size() | 报告栈的规模 |
empty() | 判断栈是否为空 |
push(e) | 将元素 e 插至栈顶(入栈) |
pop() | 删除栈顶对象,并返回该对象的引用(出栈) |
top() | 引用栈顶对象 |
根据以上关于栈的约定以及操作接口我们不难看出,栈最底部的元素是最先入栈的,而栈顶元素是最后入栈的,但是出栈时则倒了过来,栈顶元素先出栈,栈底元素最后出栈。所以,栈中元素遵循「后进先出」(last-in-first-out,LIFO)的规律。
其实生活中我们经常会发现栈的影子,比如一摞碗,只有最顶端的碗才能被取走,最后被取走的那只碗一定是最先被放到那里的那只。再比如,手枪弹匣里面的子弹,也是典型的栈结构,往弹匣里压子弹相当于入栈,而发射子弹则是出栈。
实现
由于栈也是序列结构,所以我们可以借助于前面文章中讲过的向量或者是链表来实现栈。这样,我们就不必再纠结于数据结构的内部实现机制,而是更好地把精力放到它的外部特性上面。如果有读者对于向量或者链表不熟悉,可以看我之前写过的文章:手把手教你实现一个向量,手把手教你实现一个链表。
下面代码中用到的头文件 Vector.h 和 LinkedList.h 已上传至 GitHub,点击这里可获得。
基于向量的实现
栈的实现代码如下:
#include "Vector.h"
template <typename T> class Stack { //将向量的首端作为栈底,末端作为栈顶
private:
Vector<T> v;
public:
//返回栈的规模(栈中元素的个数)
int size() {
return v.size();
}
//判断栈是否为空
bool empty() {
return v.empty();
}
//入栈,等效于将新元素作为向量的末元素插入
void push(T const& e) { //入栈
v.insert(size(), e);
}
//出栈,等效于删除向量的末元素
T pop() {
return v.remove(size() - 1);
}
//取顶,直接返回向量的末元素
T& top() {
return v[v.size() - 1];
}
};
基于链表的实现
栈的实现如下:
#include "LinkedList.h"
template <typename T> class Stack {
private:
LinkedList<T> list;
public:
//返回栈的规模(栈中元素的个数)
int size() {
return list.size();
}
//判断栈是否为空
bool empty() {
return list.empty();
}
//入栈,等效于将新元素作为链表的末节点插入
void push(T const& e) { //入栈
list.insertAsLast(e);
}
//出栈,等效于删除链表的末节点
T pop() {
return list.remove(list.last());
}
//取顶,直接返回链表的末节点
T& top() {
return list.last()->data;
}
};
总结
借助于我们之前写过的向量和链表,栈实现起来并不难,短短几十行代码即可搞定,不过,代码虽然简单,栈的用处可不小,接下来的文章我会向大家介绍栈的具体应用,请大家拭目以待吧!请大家拭目以待吧!本文代码已上传至 GitHub,点击这里即可获得。
欢迎关注我的微信公众号,扫描下方二维码或微信搜索:AProgrammer,就可以找到我,我会持续为你分享 IT 技术。