目录
引言
在上一篇文章中给读者介绍了数据结构中较为简单的顺序表和链表,此篇文章介绍的是在其基础上衍生的栈和队列,四种结构均为线性结构。
一、栈
1.1 概念
栈是一种具有后进先出(Last-In-First-Out,LIFO)特性的线性数据结构。想象一下,你有一堆书放在桌子上,每当你放下一本书时,它会被放在最顶部。当你需要取出一本书时,你只能从最顶部开始取。这就是栈的工作方式。
或者说栈一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出的原则。
1.2 操作
- Push:将元素压入栈顶。
- Pop:从栈顶弹出元素。
- Top:返回栈顶元素的值。
- IsEmpty:检查栈是否为空。
1.3 实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,不过实际应用需要更加灵活的结构,一般使用动态栈,笔者所实现的也是动态方式。
Stack.h
#ifndef STACK_STACK_H
#define STACK_STACK_H
#include <cstddef>
typedef int STDataType;
class Stack {
private:
STDataType *_data; //动态开辟栈空间
size_t _top; //指向栈顶元素的下一个位置
size_t _capacity; //栈的容量
public:
//构造函数初始化内部属性并开辟动态空间
explicit Stack(size_t capacity = 4);
//析构函数释放开辟内存
~Stack();
//进栈操作
void push(STDataType value);
//出栈操作
void pop();
//判断栈是否为空
bool isEmpty();
};
#endif //STACK_STACK_H
Stack.cpp
#include "Stack.h"
// 构造函数初始化内部属性并开辟动态空间
Stack::Stack(size_t capacity) {
_data = new STDataType[capacity];
_top = 0;
_capacity = capacity;
}
// 析构函数释放开辟内存
Stack::~Stack() {
delete[] _data;
}
// 进栈操作
void Stack::push(STDataType value) {
if (_top == _capacity) {
// 栈满时扩展容量
size_t newCapacity = _capacity * 2;
STDataType* newData = new STDataType[newCapacity];
for (size_t i = 0; i < _capacity; ++i) {
newData[i] = _data[i];
}
delete[] _data;
_data = newData;
_capacity = newCapacity;
}
_data[_top++] = value;
}
// 出栈操作
void Stack::pop() {
if (_top > 0) {
--_top;
}
}
// 判断栈是否为空
bool Stack::isEmpty() {
return (_top == 0);
}
1.4 应用场景
- 函数调用:栈用于存储函数调用的上下文信息,包括局部变量、返回地址等。
- 括号匹配:栈可以用于检查表达式中的括号是否匹配。
- 浏览器历史记录:浏览器使用栈来跟踪访问的网页,允许用户使用"后退"按钮返回上一个页面。
二、队列
2.1 概念
队列是一种具有先进先出(First-In-First-Out,FIFO)特性的线性数据结构。可以想象为排队等候的人们,先到先得,后到后得。
或者说队列是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out)特性。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
2.2 操作
- Enqueue:将元素添加到队列的末尾。
- Dequeue:从队列的前端移除元素。
- Front:返回队列的第一个元素。
- IsEmpty:检查队列是否为空。
2.3 实现
Queue.h
#ifndef QUEUE_QUEUE_H
#define QUEUE_QUEUE_H
#include <cstddef>
typedef int QDataType;
class Queue {
private:
struct Node {
QDataType data;
Node* next;
Node(QDataType value) : data(value), next(nullptr) {}
};
Node* _front; // 队列的前端
Node* _rear; // 队列的后端
public:
// 构造函数初始化内部属性
Queue();
// 析构函数释放内存
~Queue();
// 入队操作
void enqueue(QDataType value);
// 出队操作
void dequeue();
// 返回队列的第一个元素
QDataType front();
// 判断队列是否为空
bool isEmpty();
};
#endif //QUEUE_QUEUE_H
Queue.cpp
#include "Queue.h"
Queue::Queue() : _front(nullptr), _rear(nullptr) {}
Queue::~Queue() {
while (_front != nullptr) {
Node* temp = _front;
_front = _front->next;
delete temp;
}
}
void Queue::enqueue(QDataType value) {
Node* newNode = new Node(value);
if (_rear == nullptr) {
// 如果队列为空,则新节点同时成为队列的前端和后端
_front = newNode;
_rear = newNode;
} else {
// 如果队列不为空,则将新节点添加到队列的后端
_rear->next = newNode;
_rear = newNode;
}
}
void Queue::dequeue() {
if (_front != nullptr) {
// 删除队列的前端节点并更新_front指针
Node* temp = _front;
_front = _front->next;
if (_front == nullptr) {
// 如果队列为空,则更新_rear指针
_rear = nullptr;
}
delete temp;
}
}
QDataType Queue::front() {
if (_front != nullptr) {
// 返回队列的第一个元素值
return _front->data;
}
// 队列为空时的处理,这里假设元素类型为int,可以根据实际情况进行修改
return 0;
}
bool Queue::isEmpty() {
// 判断队列是否为空
return (_front == nullptr);
}
1.4 应用场景
- 广度优先搜索(BFS):队列在BFS算法中用于管理待处理的节点。
- 缓冲区管理:队列常用于处理和管理输入/输出请求,确保按照请求的顺序进行处理。
- 多线程任务调度:队列可以用于管理多个线程之间的任务调度。
三、栈和队列的比较
虽然栈和队列在某些方面很相似(都是线性数据结构),但它们的主要区别在于数据的插入和删除方式。栈从一端进行插入和删除操作,而队列则分别从两端进行这些操作。
在性能方面,栈和队列的插入和删除操作的时间复杂度都是O(1),即常数时间。但是,对于栈和队列的其他操作,如访问特定元素或搜索特定元素,其时间复杂度可能会更高,取决于实现方式。
四、总结
栈和队列是计算机科学中重要的数据结构,它们在算法和编程中被广泛使用。栈具有后进先出的特性,适用于函数调用、括号匹配等场景。而队列则具有先进先出的特性,适用于广度优先搜索、任务调度等场景。
理解栈和队列的原理和特性,以及它们的应用场景,对于成为一名优秀的程序员至关重要。希望本文对读者有所帮助,进一步拓宽对数据结构的认识和理解。