栈
文章目录
本篇文章仅仅展示用C++实现栈
如若想深入了解栈,请移步数据结构专栏中寻找[栈]的文章(用Java写的)
栈的常用操作
cpp
#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
// 栈的常用操作
void Stack()
{
// 初始化栈
stack<int> stack;
// 元素入栈
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(6);
stack.push(4);
// 访问栈顶元素
int top = stack.top();
// 元素出栈
stack.pop(); // 无返回值
// 获取栈的长度
int size = stack.size();
// 判断是否为空
bool empty = stack.empty();
}
基于链表实现栈
cpp
#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
// 栈的实现
// 1. 基于链表的实现
class ListNode {
public:
int value;
ListNode* next;
ListNode(int val) {
value = val;
next = nullptr;
}
};
class LinkedListStack {
private:
ListNode* stackTop; // 将头节点作为栈顶
int stackSize; // 栈的长度
public:
LinkedListStack() {
stackTop = nullptr;
stackSize = 0;
}
~LinkedListStack() {
// 遍历链表删除节点,释放内存
freeMemoryLinkedList(stackTop);
}
void freeMemoryLinkedList(ListNode* node) {
if (node == nullptr) {
return;
}
freeMemoryLinkedList(node->next);
delete node;
}
// 获取栈的长度
int size()
{
return stackSize;
}
// 判断栈是否为空
bool isEmpty()
{
return size() == 0;
}
// 入栈
void push(int num)
{
ListNode* node = new ListNode(num);
node->next = stackTop;
stackTop = node;
stackSize++;
}
// 出栈
void pop()
{
int num = top();
ListNode* tmp = stackTop;
stackTop = stackTop->next;
// 释放内存
delete tmp;
stackSize--;
}
// 访问栈顶元素
int top()
{
if (isEmpty())
throw out_of_range("栈为空");
return stackTop->value;
}
// 将 List 转化为 Array 并返回
vector<int> toVector()
{
ListNode* node = stackTop;
vector<int> res(size());
for (int i = res.size() - 1; i >= 0; i--)
{
res[i] = node->value;
node = node->next;
}
return res;
}
};
基于数组实现栈
cpp
#define _CRT_SECURE_NO_WARNINGS 1
// hello 算法 第五章 栈和队列
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
// 2. 基于数组实现栈
class ArrayStack {
private:
vector<int> stack;
public:
// 获取栈的长度
int size()
{
return stack.size();
}
// 判断是否为空
bool isEmpty()
{
return stack.size() == 0;
}
// 入栈
void push(int num)
{
stack.push_back(num);
}
// 出栈
void pop()
{
int oldTop = top();
stack.pop_back();
}
// 访问栈顶元素
int top()
{
if (isEmpty())
throw out_of_range("栈为空");
return stack.back();
}
// 返回 Vector
vector<int> toVector()
{
return stack;
}
};
两种实现方法对比
支持操作
两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。
时间效率
在基于数组的实现中,入栈和出栈操作都在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为 O(n)
。
在基于链表的实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。
综上所述,当入栈与出栈操作的元素是基本数据类型时,例如 int
或 double
,我们可以得出以下结论。
- 基于数组实现的栈在触发扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。
- 基于链表实现的栈可以提供更加稳定的效率表现。
空间效率
在初始化列表时,系统会为列表分配"初始容量",该容量可能超出实际需求;并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容的,扩容后的容量也可能超出实际需求。因此,基于数组实现的栈可能造成一定的空间浪费。
然而,由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大。
综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。
栈的应用
- 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
- 程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。