STL模板中的队列与栈详解
发布时间: 2023-12-16 06:42:45 阅读量: 43 订阅数: 35
# 1. STL简介
## 1.1 STL(标准模板库)概述
STL(Standard Template Library)是C++标准库的一个重要组成部分,它提供了一套丰富的模板类和算法,可以大大提高C++程序的开发效率和运行性能。STL包含了容器(Container)、算法(Algorithm)和迭代器(Iterator)三个重要组件。
## 1.2 STL中的容器、算法和迭代器
### 1.2.1 容器
容器是STL中用于存储和管理数据的模板类,它可以分为序列式容器(Sequence Container)和关联式容器(Associative Container)。序列式容器以元素在容器中的位置来访问元素,包括向量(vector)、链表(list)、双端队列(deque)、数组(array)等。关联式容器以元素的键值来访问元素,包括集合(set)、映射(map)、哈希映射(unordered_map)等。
### 1.2.2 算法
算法是STL中用于操作和处理容器中数据的模板函数,它包括了非修改序列算法(Non-modifying Sequence Algorithm)、修改序列算法(Modifying Sequence Algorithm)、排序和查找算法等,比如排序(sort)、查找(find)、计数(count)等。
### 1.2.3 迭代器
迭代器是STL中用于遍历容器中元素的模板类,它相当于一个指针,可以指向容器中的某个位置,并且可以进行自增、自减等操作。迭代器分为输入迭代器(Input Iterator)、输出迭代器(Output Iterator)、前向迭代器(Forward Iterator)、双向迭代器(Bidirectional Iterator)和随机访问迭代器(Random Access Iterator)等。
在STL中,容器、算法和迭代器三个组件紧密结合,通过迭代器对容器进行遍历和访问,再结合各种算法对容器中的数据进行操作和处理,极大地提高了开发效率。
以上是STL简介的内容,下面将逐步展开对队列和栈的详解。
# 2. 队列的原理与实现
队列是一种常用的数据结构,它遵循先进先出(FIFO)的原则。在STL(标准模板库)中,也提供了队列的实现,方便开发者在各种场景下使用。本章将介绍队列的原理与实现,并详细介绍STL中队列的使用方法。
### 2.1 队列的基本概念和特点
队列是一种线性数据结构,具有以下特点:
- 元素的插入操作只能在队列的一端进行,称为队尾(rear);
- 元素的删除操作只能在队列的另一端进行,称为队首(front);
- 队列遵循先进先出的原则,即最先插入的元素最先被删除。
队列常用的操作包括入队(enqueue)和出队(dequeue)操作。入队即将元素插入到队尾,出队即将队首元素删除。
### 2.2 队列的实现方式
队列可以通过数组或链表来实现,每种实现方式都有其优缺点。
#### 2.2.1 数组实现队列
数组实现的队列需要两个指针,分别指向队首和队尾。入队操作时,将元素插入到队尾,并更新队尾指针;出队操作时,删除队首元素,并更新队首指针。数组实现的队列需要声明一个固定大小的数组,在使用过程中容量是固定的,不支持动态扩容。
以下是使用数组实现队列的示例代码(使用Python语言实现):
```python
class Queue:
def __init__(self):
self.queue = []
def enqueue(self, item):
self.queue.append(item)
def dequeue(self):
if self.is_empty():
return None
else:
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
def size(self):
return len(self.queue)
```
#### 2.2.2 链表实现队列
链表实现的队列使用两个指针,一个指向队首节点,另一个指向队尾节点。入队操作时,将元素插入到队尾,并更新队尾指针;出队操作时,删除队首节点,并更新队首指针。链表实现的队列支持动态扩容,不受固定大小的限制。
以下是使用链表实现队列的示例代码(使用Java语言实现):
```java
class QueueNode {
int data;
QueueNode next;
public QueueNode(int data) {
this.data = data;
this.next = null;
}
}
class Queue {
QueueNode front, rear;
public Queue() {
this.front = this.rear = null;
}
public void enqueue(int item) {
QueueNode newNode = new QueueNode(item);
if (this.rear == null) {
this.front = this.rear = newNode;
} else {
this.rear.next = newNode;
this.rear = newNode;
}
}
public int dequeue() {
if (this.front == null) {
return -1;
}
int item = this.front.data;
this.front = this.front.next;
if (this.front == null) {
this.rear = null;
}
return item;
}
public boolean isEmpty() {
return this.front == null;
}
public int size() {
QueueNode current = this.front;
int count = 0;
while (current != null) {
count++;
current = current.next;
}
return count;
}
}
```
### 2.3 STL中队列的使用方法
在STL中,队列的实现保存在`queue`头文件中。要使用队列,首先需要包含该头文件。使用STL中的队列可以省去手动实现队列的过程,方便快捷。
以下是使用STL中队列的示例代码(使用C++语言实现):
```cpp
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while (!q.empty()) {
std::cout << q.front() << " ";
q.pop();
}
return 0;
}
```
在上述示例中,首先创建了一个`std::queue`对象`q`,调用`push`方法向队列中插入元素,调用`front`方法获取队首元素,调用`pop`方法删除队首元素。最后,通过循环遍历队列,依次输出队列中的元素。
以上是队列的原理与实现的详细介绍,以及STL中队列的使用方法。在实际开发中,可以根据实际需求选择合适的实现方式。下一章将介绍队列的应用场景与实例。
# 3. 队列的应用场景与实例
队列作为一种先进先出(FIFO)的数据结构,在实际应用中具有广泛的应用场景。下面将介绍两个典型的队列应用场景并给出相应的示例代码。
#### 3.1 生产者-消费者模型中队列的应用
在多线程编程中,生产者-消费者模型是一种常见的并发模式,通过队列作为生产者和消费者之间的缓冲区,实现了线程间的解耦和协作。生产者向队列中添加数据,消费者从队列中获取数据,实现了数据的安全共享和消息传递。
示例代码(Python实现):
```python
import queue
import threading
import time
# 定义一个队列作为缓冲区
q = queue.Queue(maxsize=5)
# 生产者线程
def producer():
for i in range(10):
q.put(f'Product {i}')
print(f'Produced Product {i}')
time.sleep(1)
# 消费者线程
def consumer():
while True:
product = q.get()
print(f'Consumed {product}')
time.sleep(2)
q.task_done()
# 创建生产者和消费者线程
p_thread = threading.Thread(target=producer)
c_thread = threading.Thread(target=consumer)
# 启动线程
p_thread.start()
c_thread.start()
# 等待线程结束
p_thread.join()
c_thread.join()
```
代码总结:上述代码中,通过Python的queue模块实现了生产者-消费者模型,其中生产者线程不断向队列中生产数据,而消费者线程不断从队列中消费数据,实现了生产者和消费者的解耦和协作。
结果说明:运行代码后,可以看到生产者不断地生产产品,而消费者不断地消费产品,二者之间通过队列实现了数据的安全传递和共享。
#### 3.2 队列在广度优先搜索(BFS)中的应用
在图论和树的遍历中,广度优先搜索(BFS)常常使用队列来实现,通过队列按层级逐个访问节点,从而实现了对图和树的层级遍历。
示例代码(Java实现):
```java
import java.util.*;
class Graph {
private int V;
private LinkedList<Integer> adj[];
Graph(int v) {
V = v;
adj = new LinkedList[v];
for (int i = 0; i < v; ++i)
adj[i] = new LinkedList();
}
void addEdge(int v, int w) {
adj[v].add(w);
}
void BFS(int s) {
boolean visited[] = new boolean[V];
LinkedList<Integer> queue = new LinkedList<Integer>();
visited[s] = true;
queue.add(s);
while (queue.size() != 0) {
s = queue.poll();
System.out.print(s + " ");
Iterator<Integer> i = adj[s].listIterator();
while (i.hasNext()) {
int n = i.next();
if (!visited[n]) {
visited[n] = true;
queue.add(n);
}
}
}
}
public static void main(String args[]) {
Graph g = new Graph(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
System.out.println("Following is Breadth First Traversal " + "(starting from vertex 2)");
g.BFS(2);
}
}
```
代码总结:上述代码通过Java实现了图的广度优先搜索(BFS),其中利用队列实现了按层级遍历节点,通过不断将相邻未访问节点加入队列,实现了对图的广度优先搜索。
结果说明:运行代码后,可以看到从指定节点开始的广度优先遍历结果,按层级逐个访问节点。
通过以上示例,可以清楚地了解了队列在生产者-消费者模型和广度优先搜索中的应用场景及具体示例。
# 4. 栈的原理与实现
栈是一种具有后进先出(LIFO)特性的数据结构,它只允许在栈顶进行插入和删除操作。在STL中,栈通常是通过使用vector或deque作为底层容器来实现的。
#### 4.1 栈的基本概念和特点
栈具有以下基本特点:
- 后进先出(LIFO):最后插入的元素最先被删除。
- 只能在栈顶进行插入和删除操作,不能在中间或底部进行操作。
#### 4.2 栈的实现方式
STL中的栈通常是使用vector或deque作为其底层容器来实现的,可以通过包含头文件`<stack>`来使用STL中的栈。
#### 4.3 STL中栈的使用方法
以下是在C++中使用STL栈的基本方法:
```cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> st;
// 在栈顶插入元素
st.push(1);
st.push(2);
st.push(3);
// 访问栈顶元素
std::cout << "栈顶元素为:" << st.top() << std::endl;
// 从栈顶删除元素
st.pop();
// 检查栈是否为空
if (st.empty()) {
std::cout << "栈为空" << std::endl;
} else {
std::cout << "栈不为空" << std::endl;
}
// 获取栈中元素个数
std::cout << "栈中元素个数为:" << st.size() << std::endl;
return 0;
}
```
#### 总结
栈是一种非常常用的数据结构,STL中的栈通过vector或deque作为底层容器进行实现,提供了简单而强大的操作方法。在实际应用中,栈常常用于表达式求值、深度优先搜索等场景。
以上是关于栈的原理、实现以及STL中栈的使用方法的详细介绍,希望能够对您有所帮助。
# 5. 栈的应用场景与实例
栈是一种先进后出(Last In First Out,简称LIFO)的数据结构,它具有快速插入和删除元素的特点。在STL模板中,栈是以容器适配器(Container Adapter)的形式实现的。本章将介绍栈的应用场景及相应的实例。
## 5.1 后缀表达式与栈的关系
后缀表达式(也称为逆波兰表达式)是一种不包含括号的数学表达式表示方法,其中操作数在运算符之后。栈可以很好地辅助后缀表达式的计算。例如,给定一个后缀表达式 "35+2*",我们可以使用栈来求解其结果。
```python
def evaluate_postfix(expression):
stack = []
operators = set(["+", "-", "*", "/"])
for token in expression:
if token not in operators:
stack.append(int(token))
else:
b = stack.pop()
a = stack.pop()
if token == "+":
result = a + b
elif token == "-":
result = a - b
elif token == "*":
result = a * b
else:
result = a / b
stack.append(result)
return stack.pop()
expression = "35+2*"
result = evaluate_postfix(expression)
print("The result of postfix expression", expression, "is:", result)
```
代码解析:首先,我们定义一个空栈和一个操作符集合。然后遍历后缀表达式中的每个字符,如果字符不是操作符,则将其转换为整数并压入栈中;如果字符是操作符,则从栈中弹出两个操作数,进行相应的运算,并将结果压入栈中。最后,输出栈中的最后一个元素,即为后缀表达式的计算结果。
运行结果:
```
The result of postfix expression 35+2* is: 16
```
通过使用栈,我们能够方便地计算后缀表达式的结果。
## 5.2 栈在深度优先搜索(DFS)中的应用
深度优先搜索(Depth First Search,简称DFS)是一种经典的图遍历算法。在DFS的过程中,栈可以帮助我们记录搜索的路径,并回溯到上一个节点继续搜索。下面我们以一个简单的迷宫问题为例来说明栈在DFS中的应用。
```python
def dfs(matrix, start, end):
stack = [start]
visited = set()
while stack:
node = stack.pop()
visited.add(node)
if node == end:
return True
neighbors = get_neighbors(node, matrix)
for neighbor in neighbors:
if neighbor not in visited:
stack.append(neighbor)
return False
def get_neighbors(node, matrix):
rows, cols = len(matrix), len(matrix[0])
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
neighbors = []
for direction in directions:
row = node[0] + direction[0]
col = node[1] + direction[1]
if 0 <= row < rows and 0 <= col < cols and matrix[row][col] != 1:
neighbors.append((row, col))
return neighbors
matrix = [
[0, 0, 0, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 1, 0],
[1, 1, 1, 0, 0],
[0, 0, 0, 0, 0]
]
start = (0, 0)
end = (4, 4)
path_exists = dfs(matrix, start, end)
print("Does a path exist from", start, "to", end, "?", path_exists)
```
代码解析:我们使用一个二维数组表示迷宫,其中0表示可以走的路径,1表示墙壁。首先,我们定义一个空栈和一个记录已访问节点的集合。然后,将起始节点压入栈中,并将其标记为已访问。在每一次循环中,将栈顶节点弹出,并将其标记为已访问。然后,获取当前节点的相邻节点,如果相邻节点未被访问且为可走路径,则将其压入栈中。如果栈为空,则表示不存在从起始节点到目标节点的路径;否则,表示存在路径。
运行结果:
```
Does a path exist from (0, 0) to (4, 4)? True
```
通过使用栈,我们可以实现深度优先搜索算法,并找到起始节点到目标节点的路径。
在本章节中,我们介绍了栈的两个典型应用场景:后缀表达式的计算和深度优先搜索算法。栈在这些场景中发挥了重要的作用,为我们解决问题提供了便利。通过学习栈的应用实例,我们可以更好地理解和使用STL模板中的栈容器。
# 6. 队列与栈的比较与选择
在实际的编程应用中,队列和栈都是常用的数据结构,它们各自具有特定的特点和适用场景。在选择使用队列还是栈时,需考虑到数据处理的实际需求以及性能方面的比较。
#### 6.1 队列与栈的性能比较
- 在元素的插入与删除操作上,队列和栈的性能相对来说是比较接近的,都可以在O(1)的时间复杂度内完成。
- 在使用空间上,队列和栈都是使用动态内存分配的,所以在空间利用率方面也是接近的。
#### 6.2 队列与栈的选择与实际应用建议
- 当需要先进先出(FIFO)的数据结构时,选择队列;例如在实现广度优先搜索(BFS)时,队列是一个非常适合的数据结构。
- 当需要先进后出(FILO)的数据结构时,选择栈;比如在实现深度优先搜索(DFS)和处理后缀表达式时,栈是更加合适的选择。
根据实际需求选择使用队列还是栈,能够更好地提高程序的性能和可读性,并且更好地满足数据处理的需求。
希望这篇文章对你有所帮助,如果需要进一步了解其他章节内容,或有其他问题,都可以继续和我交流。
0
0