栈(Stack)是一种特殊的线性表,其只允许在表的一端(称为栈顶)进行插入和删除元素的操作,而另一端(称为栈底)则不允许进行操作。栈遵循后进先出(Last In First Out,LIFO)的原则,即最后插入的元素会最先被删除。以下是栈的原理和实现的详细介绍:
栈的原理
- 定义与特性:
- 栈是一种操作受限的线性表,只能在一端(栈顶)进行插入(入栈/压栈)和删除(出栈)操作。
- 栈遵循后进先出(LIFO)的原则,即最后插入的元素会最先被删除。
- 栈底是固定的一端,不允许进行操作;栈顶是动态变化的一端,所有操作都在此进行。
- 应用场景:
- 栈在程序设计中有着广泛的应用,如函数调用栈、浏览器的前进后退功能、括号匹配等。
- 在数据结构中,栈也常用于实现深度优先搜索(DFS)等算法。
栈的实现
栈的实现方式主要有两种:基于数组的静态栈和基于动态内存分配的动态栈,以及基于链表的链式栈。
静态栈(基于数组)
- 定义:静态栈在编译时就确定了其大小,不能在运行时改变。
- 实现:
- 使用一个数组来存储栈中的元素。
- 使用一个整型变量(如
top
)来记录栈顶元素的位置。栈空时,top
通常指向栈底元素的下一个位置(即-1
或0
,取决于具体实现);栈满时,top
等于数组长度减一。 - 入栈操作:检查栈是否已满,若未满则将新元素放在
top+1
的位置,并将top
加一。 - 出栈操作:检查栈是否为空,若不为空则将
top
减一,并返回top
指向的元素。
动态栈(基于动态内存分配)
- 定义:动态栈的大小可以在运行时根据需要动态调整。
- 实现:
- 使用动态分配的数组(如通过
malloc
或new
分配的内存)来存储栈中的元素。 - 类似静态栈,使用一个整型变量(如
top
)来记录栈顶元素的位置。 - 当栈满时,可以通过重新分配更大的内存空间来扩展栈的容量。
- 入栈和出栈操作与静态栈类似,但需要处理内存分配和释放的逻辑。
- 使用动态分配的数组(如通过
链式栈(基于链表)
- 定义:链式栈使用链表来实现栈的存储结构。
- 实现:
- 每个节点包含数据域和指向下一个节点的指针。
- 栈顶指针指向链表的头节点(或尾节点,取决于具体实现)。
- 入栈操作:创建一个新节点,将其数据域设置为要入栈的元素,并将其指针指向当前栈顶节点的下一个位置,然后更新栈顶指针指向新节点。
- 出栈操作:检查栈是否为空,若不为空则获取栈顶节点的数据域的值,将栈顶指针指向下一个节点,并释放原栈顶节点的内存(如果需要)。
实现栈的代码可以根据你选择的数据结构(如数组或链表)和是否使用静态或动态内存分配而有所不同。下面我将分别给出基于数组的静态栈和基于链表的链式栈的简单实现示例。
基于数组的静态栈实现(C++)
#include <iostream>
#include <stdexcept> // 用于抛出异常
template<typename T, size_t MAXSIZE>
class ArrayStack {
private:
T data[MAXSIZE];
size_t top;
public:
ArrayStack() : top(-1) {}
bool isEmpty() const {
return top == -1;
}
bool isFull() const {
return top == MAXSIZE - 1;
}
void push(const T& item) {
if (isFull()) {
throw std::out_of_range("Stack is full");
}
data[++top] = item;
}
T pop() {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
return data[top--];
}
T topElement() const {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
return data[top];
}
};
// 使用示例
int main() {
ArrayStack<int, 10> stack;
stack.push(1);
stack.push(2);
std::cout << "Top element: " << stack.topElement() << std::endl; // 输出 2
std::cout << "Popped element: " << stack.pop() << std::endl; // 输出 2
std::cout << "New top element: " << stack.topElement() << std::endl; // 输出 1
return 0;
}
基于链表的链式栈实现(C++)
#include <iostream>
#include <stdexcept> // 用于抛出异常
template<typename T>
class Node {
public:
T data;
Node* next;
Node(const T& item) : data(item), next(nullptr) {}
};
template<typename T>
class LinkedStack {
private:
Node<T>* top;
public:
LinkedStack() : top(nullptr) {}
~LinkedStack() {
while (!isEmpty()) {
pop();
}
}
bool isEmpty() const {
return top == nullptr;
}
void push(const T& item) {
Node<T>* newNode = new Node<T>(item);
newNode->next = top;
top = newNode;
}
T pop() {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
Node<T>* temp = top;
T poppedData = top->data;
top = top->next;
delete temp;
return poppedData;
}
T topElement() const {
if (isEmpty()) {
throw std::out_of_range("Stack is empty");
}
return top->data;
}
};
// 使用示例
int main() {
LinkedStack<int> stack;
stack.push(1);
stack.push(2);
std::cout << "Top element: " << stack.topElement() << std::endl; // 输出 2
std::cout << "Popped element: " << stack.pop() << std::endl; // 输出 2
std::cout << "New top element: " << stack.topElement() << std::endl; // 输出 1
return 0;
}
这两个示例分别展示了如何使用数组和链表来实现栈的基本操作:push
(入栈)、pop
(出栈)和topElement
(获取栈顶元素)。注意,静态栈的大小在编译时就确定了,而链式栈的大小可以动态地根据需要增长。在链式栈的实现中,还包含了析构函数来释放链表占用的内存,以避免内存泄漏。
总结
栈是一种非常重要的数据结构,其原理和实现方式多种多样。在实际应用中,可以根据具体需求选择合适的实现方式。无论是静态栈、动态栈还是链式栈,它们都遵循后进先出的原则,并在不同的场景下发挥着重要的作用。