数据结构、算法与应用:C++语言描述(第2版)

前言

《数据结构、算法与应用:C++语言描述》是一本经典的计算机科学教材,由 Sartaj Sahni 教授编写。本书以C++为实现工具,系统阐述了数据结构的设计原理、算法分析方法及实际应用场景,是学习数据结构与算法的权威参考资料。本笔记基于第2版内容,对核心知识点、关键算法的C++实现、时间复杂度分析及应用场景进行详细梳理,旨在为读者提供全面且深入的学习参考。

目标读者

本笔记适合以下读者群体:

  • 计算机科学专业学生:深入理解数据结构与算法的理论基础和C++实现
  • 软件工程师:需要在实际项目中应用高效的数据结构和算法
  • 算法竞赛参与者:提升算法设计和分析能力
  • 技术面试官:掌握数据结构与算法的核心概念和C++实现细节

C++在数据结构与算法中的优势

C++作为实现数据结构和算法的首选语言之一,具有以下优势:

  1. 模板系统:通过模板实现泛型数据结构,提高代码复用性和类型安全性
  2. STL库:提供丰富的标准容器和算法,为数据结构和算法的实现提供基础
  3. 内存管理:支持手动内存管理,允许更精细的内存控制和优化
  4. 性能优势:编译型语言,执行效率高,适合对性能要求严格的算法
  5. 面向对象:支持封装、继承和多态,便于设计模块化的数据结构
  6. 操作符重载:允许为自定义数据类型定义操作符,提高代码可读性
  7. 异常处理:提供异常机制,增强代码的健壮性

本笔记的核心价值

  1. C++高级特性的深度应用

    • 模板特化和偏特化
    • 右值引用和移动语义
    • 智能指针的使用
    • 类型萃取(type traits)
    • 表达式模板
  2. 算法分析的深入

    • 均摊分析
    • 势能法分析
    • amortized analysis
    • 最坏情况、平均情况和最好情况分析
    • 空间复杂度的详细分析
  3. 性能优化策略

    • 缓存优化
    • 内存布局优化
    • 分支预测优化
    • 并行化策略
    • 编译器优化提示
  4. 实际应用场景

    • 大规模数据处理
    • 实时系统中的算法选择
    • 分布式系统中的数据结构
    • 嵌入式系统中的内存受限场景
    • 高性能计算中的算法优化
  5. 完整的C++实现

    • 符合现代C++标准(C++11/14/17/20)
    • 包含完整的错误处理和边界检查
    • 提供详细的性能测试和分析
    • 代码风格统一,可读性强

本笔记不仅关注理论知识的讲解,更注重实际应用能力的培养,通过丰富的代码示例和详细的性能分析,帮助读者掌握数据结构与算法的核心原理和C++实现技巧,为解决实际问题提供有力的工具和方法。

1. 算法分析基础

1.1 基本概念

  • 数据结构:数据元素及其相互关系的集合,如数组、链表、树、图等。数据结构的选择直接影响算法的效率。
  • 算法:解决特定问题的有限步骤集合,需满足有穷性、确定性、可行性、输入和输出五大特征。
  • 时间复杂度:衡量算法执行时间随输入规模 n 增长的变化趋势,使用大O记号表示。
  • 空间复杂度:衡量算法所需额外空间随输入规模增长的变化趋势,同样使用大O记号表示。
  • 渐近复杂度:当输入规模趋近于无穷大时的复杂度行为,是算法分析的重点。
  • 算法效率:包括时间效率和空间效率,通常需要在两者之间进行权衡。

1.2 复杂度记号

1.2.1 大O记号(O-notation)

若存在正常数 c 和 n_0 ,使得当 n eq n_0 时,有 T(n) eq c dot f(n) ,则称 T(n) = O(f(n)) 。大O记号描述了算法时间复杂度的上界

1.2.2 大Ω记号(Ω-notation)

若存在正常数 c 和 n_0 ,使得当 n eq n_0 时,有 T(n) ge c dot f(n) ,则称 T(n) = Ω(f(n)) 。大Ω记号描述了算法时间复杂度的下界

1.2.3 大Θ记号(Θ-notation)

若存在正常数 c_1, c_2 和 n_0 ,使得当 n eq n_0 时,有 c_1 dot f(n) eq T(n) eq c_2 dot f(n) ,则称 T(n) = Θ(f(n)) 。大Θ记号描述了算法时间复杂度的紧确界

1.2.4 小o记号(o-notation)

若对于任意正常数 c ,存在 n_0 ,使得当 n eq n_0 时,有 T(n) < c dot f(n) ,则称 T(n) = o(f(n)) 。小o记号描述了算法时间复杂度的非紧上界

1.3 常见时间复杂度比较

按增长率从小到大排序:

O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

复杂度类别分析

  • 常数时间 O(1):操作时间与输入规模无关,如数组随机访问、哈希表查找
  • 对数时间 O(log n):操作时间随输入规模对数增长,如二分查找、平衡二叉树操作
  • 线性时间 O(n):操作时间与输入规模线性相关,如线性搜索、数组遍历
  • 线性对数时间 O(n log n):操作时间与输入规模成线性对数关系,如高效排序算法(归并排序、快速排序平均情况)
  • 多项式时间 O(nᵏ):操作时间与输入规模成多项式关系,如冒泡排序、选择排序
  • 指数时间 O(2ⁿ):操作时间随输入规模指数增长,如子集生成、汉诺塔问题
  • 阶乘时间 O(n!):操作时间随输入规模阶乘增长,如旅行商问题的暴力解法

1.4 算法分析方法

1.4.1 循环分析

  • 单循环:循环次数乘以循环体时间复杂度
  • 嵌套循环:各层循环次数的乘积
  • 并列循环:各循环时间复杂度的总和
  • 复杂循环:需要分析循环变量的变化规律

1.4.2 递归分析

  • 递归树法:将递归过程分解为树结构,计算每一层的时间复杂度
  • 主定理(Master Theorem):用于分析分治算法的时间复杂度
  • 代入法:假设递归式的解,然后通过数学归纳法证明
  • 递推关系式:建立递归调用的时间复杂度递推关系,然后求解

1.4.3 均摊分析

  • 聚合分析:考虑所有操作的总时间,然后平均到每个操作
  • 记账法:为不同操作分配不同的时间成本,确保总时间不超过上限
  • 势能法:定义势能函数,将操作的时间复杂度与势能变化相关联

1.4.4 最坏情况、平均情况和最好情况分析

  • 最坏情况:算法在所有可能输入下的最大运行时间
  • 平均情况:算法在所有可能输入下的期望运行时间
  • 最好情况:算法在所有可能输入下的最小运行时间

1.5 空间复杂度分析

  • 输入空间:存储输入数据所需的空间
  • 暂存空间:算法运行过程中临时使用的空间
  • 输出空间:存储输出结果所需的空间

空间复杂度分析通常关注额外空间,即除输入和输出外的暂存空间。

1.6 C++中的时间复杂度分析示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 计算数组元素和的时间复杂度:O(n)
template <typename T>
T sum(const vector<T>& arr) {
T total = 0;
for (size_t i = 0; i < arr.size(); i++) { // n次循环
total += arr[i]; // 常数时间操作
}
return total;
}

// 嵌套循环的时间复杂度:O(n²)
template <typename T>
void printPairs(const vector<T>& arr) {
for (size_t i = 0; i < arr.size(); i++) { // n次外层循环
for (size_t j = 0; j < arr.size(); j++) { // n次内层循环
cout << arr[i] << " " << arr[j] << endl; // 常数时间操作
}
}
}

// 二分查找的时间复杂度:O(log n)
template <typename T>
int binarySearch(const vector<T>& arr, const T& target) {
int left = 0;
int right = arr.size() - 1;
while (left <= right) { // 最多执行log₂n次
int mid = left + (right - left) / 2; // 避免整数溢出
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}

// 归并排序的时间复杂度:O(n log n)
template <typename T>
void merge(vector<T>& arr, int left, int mid, int right) {
vector<T> temp(right - left + 1);
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int p = 0; p < temp.size(); p++) {
arr[left + p] = temp[p];
}
}

template <typename T>
void mergeSort(vector<T>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid); // 递归排序左半部分
mergeSort(arr, mid + 1, right); // 递归排序右半部分
merge(arr, left, mid, right); // 合并两个有序子数组
}
}

1.7 主定理(Master Theorem)

对于形如 T(n) = aT(n/b) + f(n) 的递归式,其中 a ≥ 1, b > 1 是常数, f(n) 是渐近正函数,则:

  1. 情况1:若 f(n) = O(n^(log_b a - ε)) 对某个 ε > 0 成立,则 T(n) = Θ(n^(log_b a))
  2. 情况2:若 f(n) = Θ(n^(log_b a) log^k n) 对某个 k ≥ 0 成立,则 T(n) = Θ(n^(log_b a) log^(k+1) n)
  3. 情况3:若 f(n) = Ω(n^(log_b a + ε)) 对某个 ε > 0 成立,且对某个常数 c < 1 和所有足够大的 n 有 a f(n/b) ≤ c f(n) ,则 T(n) = Θ(f(n))

主定理应用示例

  • 二分查找: T(n) = T(n/2) + Θ(1) ,属于情况2,结果为 Θ(log n)
  • 归并排序: T(n) = 2T(n/2) + Θ(n) ,属于情况2,结果为 Θ(n log n)
  • 快速排序(平均情况): T(n) = 2T(n/2) + Θ(n) ,属于情况2,结果为 Θ(n log n)
  • 快速排序(最坏情况): T(n) = T(n-1) + Θ(n) ,不属于主定理适用范围,结果为 Θ(n²)

1.8 实际应用中的复杂度考虑

1.8.1 常数因子的重要性

在实际应用中,当两个算法的渐近复杂度相同时,常数因子成为决定性能的关键因素。例如,插入排序在小规模数据上可能比归并排序更快,尽管其渐近复杂度更高。

1.8.2 缓存优化

现代计算机系统中,缓存对算法性能有显著影响。具有良好空间局部性的算法通常表现更好。

1.8.3 并行化潜力

某些算法比其他算法更容易并行化,这在多核系统中是一个重要考虑因素。

1.8.4 内存层次结构

算法的内存访问模式应考虑计算机的内存层次结构(寄存器、L1/L2/L3缓存、主存、磁盘),以最大化数据局部性。

1.9 C++中的性能优化技巧

  • 使用适当的容器:根据操作特点选择合适的STL容器
  • 避免不必要的复制:使用引用传递和移动语义
  • 预分配内存:对于vector等容器,使用reserve()预分配内存
  • 减少动态内存分配:使用对象池或内存池
  • 使用inline函数:减少函数调用开销
  • 避免虚函数:在性能关键路径上避免使用虚函数
  • 使用constexpr:在编译时计算常量表达式
  • 使用SIMD指令:对于向量运算,利用SIMD指令集

1.10 复杂度分析的局限性

  • 渐近分析:只关注输入规模很大时的情况,可能不适合小规模数据
  • 硬件差异:不同硬件平台的性能特性不同
  • 实现细节:算法的具体实现方式会影响实际性能
  • 输入特性:某些算法对特定类型的输入表现更好

尽管存在这些局限性,复杂度分析仍然是评估算法效率的重要工具,为算法设计和选择提供了理论基础。

2. 线性表

2.1 数组

  • 静态数组:编译时固定大小,内存连续分配,访问时间 O(1) ,插入/删除时间 O(n) 。
  • 动态数组:C++中使用 vector 实现,支持动态扩容(通常扩容为原大小的2倍)。
  • 智能数组:结合静态数组和动态数组的优点,提供更灵活的内存管理。

2.1.1 静态数组的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 静态数组模板类
template <typename T, size_t N>
class StaticArray {
private:
T data[N];
size_t size;
public:
StaticArray() : size(0) {}

// 元素访问
T& operator[](size_t index) {
if (index >= size) {
throw out_of_range("Index out of bounds");
}
return data[index];
}

const T& operator[](size_t index) const {
if (index >= size) {
throw out_of_range("Index out of bounds");
}
return data[index];
}

// 插入元素
void push_back(const T& value) {
if (size >= N) {
throw overflow_error("Array capacity exceeded");
}
data[size++] = value;
}

// 删除元素
void pop_back() {
if (size == 0) {
throw underflow_error("Array is empty");
}
size--;
}

// 获取大小
size_t getSize() const {
return size;
}

// 获取容量
constexpr size_t getCapacity() const {
return N;
}

// 检查是否为空
bool isEmpty() const {
return size == 0;
}

// 检查是否已满
bool isFull() const {
return size == N;
}

// 清空数组
void clear() {
size = 0;
}

// 迭代器
T* begin() {
return data;
}

T* end() {
return data + size;
}

const T* begin() const {
return data;
}

const T* end() const {
return data + size;
}
};

2.1.2 动态数组(vector)的深入分析

核心特性
  • 随机访问vector 支持通过索引快速访问元素,时间复杂度 O(1) 。
  • 动态扩容:当元素个数超过容量时,vector 会自动分配更大的内存空间并复制元素。
  • 尾部操作高效:在尾部插入/删除元素的时间复杂度均为 O(1) (均摊时间复杂度)。
  • 连续内存:元素存储在连续的内存空间中,具有良好的空间局部性。
  • 可移植性:跨平台兼容,是C++标准库的一部分。
扩容机制
  • 扩容策略:通常扩容为原容量的1.5倍或2倍
  • 扩容过程
    1. 分配新的更大内存块
    2. 将现有元素复制到新内存块
    3. 释放旧内存块
    4. 更新内部指针和容量
  • 均摊分析:扩容操作的均摊时间复杂度为 O(1)
性能优化技巧
  1. 预分配内存

    1
    2
    vector<int> v;
    v.reserve(1000); // 预分配1000个元素的空间
  2. 避免频繁扩容

    • 估算所需空间并提前reserve
    • 使用emplace_back代替push_back(避免额外拷贝)
  3. 内存管理

    • 使用shrink_to_fit释放多余内存
    • 注意vector的内存布局和对齐方式
  4. 移动语义

    1
    2
    3
    vector<string> v;
    string s = "hello";
    v.push_back(move(s)); // 使用移动语义,避免拷贝
  5. 批量操作

    1
    2
    3
    vector<int> v1 = {1, 2, 3};
    vector<int> v2;
    v2.insert(v2.end(), v1.begin(), v1.end()); // 批量插入
vector的实现细节
  • 内部结构:通常包含三个指针:

    • begin():指向第一个元素
    • end():指向最后一个元素的下一个位置
    • capacity():指向容量的末尾
  • 内存分配器:使用模板参数指定的分配器,默认为std::allocator<T>

  • 异常安全性:提供强异常安全保证,当扩容失败时不会丢失数据

2.1.3 数组的性能优化

缓存优化
  • 空间局部性:数组元素在内存中连续存储,访问时具有良好的空间局部性
  • 预取优化:现代CPU会自动预取连续的内存数据到缓存中
  • 缓存行对齐:合理安排数组大小,避免缓存行冲突
内存布局优化
  • 数据对齐:确保数组元素按硬件要求对齐,提高访问速度
  • 内存填充:对于某些数据结构,适当填充以避免伪共享
编译优化
  • 编译器向量化:使用#pragma omp simd或编译器内置函数启用SIMD指令
  • 循环展开:编译器会自动展开小循环,提高指令级并行性
  • 分支预测:减少数组访问中的分支,提高分支预测准确率

2.2 链表

  • 单链表:每个节点包含数据和指向下一节点的指针,插入/删除时间 O(1) (已知前驱节点时)。
  • 双链表:每个节点额外包含指向前一节点的指针,支持双向遍历。
  • 循环链表:尾节点指向头节点,形成环结构。
  • 双向循环链表:结合双链表和循环链表的特点,支持双向遍历和环形操作。

2.2.1 单链表的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// 单链表节点模板类
template <typename T>
class Node {
public:
T data;
Node<T>* next;

Node(const T& value) : data(value), next(nullptr) {}

// 移动构造函数
Node(T&& value) : data(std::move(value)), next(nullptr) {}
};

// 单链表模板类
template <typename T>
class LinkedList {
private:
Node<T>* head;
Node<T>* tail; // 尾指针,优化尾部插入操作
size_t size;
public:
LinkedList() : head(nullptr), tail(nullptr), size(0) {}

~LinkedList() {
clear();
}

// 插入节点到头部
void insertAtHead(const T& value) {
Node<T>* newNode = new Node<T>(value);
newNode->next = head;
head = newNode;
if (tail == nullptr) {
tail = newNode;
}
size++;
}

// 移动语义版本的插入
void insertAtHead(T&& value) {
Node<T>* newNode = new Node<T>(std::move(value));
newNode->next = head;
head = newNode;
if (tail == nullptr) {
tail = newNode;
}
size++;
}

// 插入节点到尾部
void insertAtTail(const T& value) {
Node<T>* newNode = new Node<T>(value);
if (head == nullptr) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
size++;
}

// 移动语义版本的插入
void insertAtTail(T&& value) {
Node<T>* newNode = new Node<T>(std::move(value));
if (head == nullptr) {
head = newNode;
tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
size++;
}

// 插入节点到指定位置(索引从0开始)
void insertAtPosition(size_t position, const T& value) {
if (position == 0) {
insertAtHead(value);
return;
}
if (position == size) {
insertAtTail(value);
return;
}
if (position > size) {
throw out_of_range("Position out of bounds");
}
Node<T>* current = head;
for (size_t i = 0; i < position - 1; i++) {
current = current->next;
}
Node<T>* newNode = new Node<T>(value);
newNode->next = current->next;
current->next = newNode;
size++;
}

// 删除头部节点
void deleteAtHead() {
if (head == nullptr) {
throw runtime_error("List is empty");
}
Node<T>* temp = head;
head = head->next;
delete temp;
size--;
if (head == nullptr) {
tail = nullptr;
}
}

// 删除尾部节点
void deleteAtTail() {
if (head == nullptr) {
throw runtime_error("List is empty");
}
if (head == tail) {
delete head;
head = nullptr;
tail = nullptr;
} else {
Node<T>* current = head;
while (current->next != tail) {
current = current->next;
}
delete tail;
tail = current;
tail->next = nullptr;
}
size--;
}

// 删除指定值的节点
void deleteByValue(const T& value) {
if (head == nullptr) {
throw runtime_error("List is empty");
}
if (head->data == value) {
deleteAtHead();
return;
}
Node<T>* current = head;
while (current->next != nullptr && current->next->data != value) {
current = current->next;
}
if (current->next == nullptr) {
throw runtime_error("Value not found");
}
Node<T>* temp = current->next;
if (temp == tail) {
tail = current;
}
current->next = current->next->next;
delete temp;
size--;
}

// 查找节点(返回索引,未找到返回-1)
int search(const T& value) const {
Node<T>* current = head;
size_t index = 0;
while (current != nullptr) {
if (current->data == value) {
return static_cast<int>(index);
}
current = current->next;
index++;
}
return -1;
}

// 打印链表
void print() const {
if (head == nullptr) {
cout << "List is empty" << endl;
return;
}
Node<T>* current = head;
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "nullptr" << endl;
}

// 清空链表
void clear() {
while (head != nullptr) {
Node<T>* temp = head;
head = head->next;
delete temp;
}
tail = nullptr;
size = 0;
}

// 获取链表大小
size_t getSize() const {
return size;
}

// 检查链表是否为空
bool isEmpty() const {
return size == 0;
}

// 获取头节点
Node<T>* getHead() const {
return head;
}

// 获取尾节点
Node<T>* getTail() const {
return tail;
}
};

2.2.2 双链表的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// 双链表节点模板类
template <typename T>
class DNode {
public:
T data;
DNode<T>* prev;
DNode<T>* next;

DNode(const T& value) : data(value), prev(nullptr), next(nullptr) {}

// 移动构造函数
DNode(T&& value) : data(std::move(value)), prev(nullptr), next(nullptr) {}
};

// 双链表模板类
template <typename T>
class DoublyLinkedList {
private:
DNode<T>* head;
DNode<T>* tail;
size_t size;
public:
DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}

~DoublyLinkedList() {
clear();
}

// 插入节点到头部
void insertAtHead(const T& value) {
DNode<T>* newNode = new DNode<T>(value);
newNode->next = head;
if (head != nullptr) {
head->prev = newNode;
}
head = newNode;
if (tail == nullptr) {
tail = newNode;
}
size++;
}

// 插入节点到尾部
void insertAtTail(const T& value) {
DNode<T>* newNode = new DNode<T>(value);
newNode->prev = tail;
if (tail != nullptr) {
tail->next = newNode;
}
tail = newNode;
if (head == nullptr) {
head = newNode;
}
size++;
}

// 插入节点到指定位置(索引从0开始)
void insertAtPosition(size_t position, const T& value) {
if (position == 0) {
insertAtHead(value);
return;
}
if (position == size) {
insertAtTail(value);
return;
}
if (position > size) {
throw out_of_range("Position out of bounds");
}

DNode<T>* current;
// 优化:根据位置选择从头部或尾部开始遍历
if (position < size / 2) {
current = head;
for (size_t i = 0; i < position - 1; i++) {
current = current->next;
}
} else {
current = tail;
for (size_t i = 0; i < size - position; i++) {
current = current->prev;
}
}

DNode<T>* newNode = new DNode<T>(value);
newNode->next = current->next;
newNode->prev = current;
if (current->next != nullptr) {
current->next->prev = newNode;
}
current->next = newNode;
size++;
}

// 删除头部节点
void deleteAtHead() {
if (head == nullptr) {
throw runtime_error("List is empty");
}
DNode<T>* temp = head;
head = head->next;
if (head != nullptr) {
head->prev = nullptr;
} else {
tail = nullptr;
}
delete temp;
size--;
}

// 删除尾部节点
void deleteAtTail() {
if (tail == nullptr) {
throw runtime_error("List is empty");
}
DNode<T>* temp = tail;
tail = tail->prev;
if (tail != nullptr) {
tail->next = nullptr;
} else {
head = nullptr;
}
delete temp;
size--;
}

// 删除指定值的节点
void deleteByValue(const T& value) {
if (head == nullptr) {
throw runtime_error("List is empty");
}
if (head->data == value) {
deleteAtHead();
return;
}
if (tail->data == value) {
deleteAtTail();
return;
}

DNode<T>* current = head->next;
while (current != tail && current->data != value) {
current = current->next;
}
if (current == tail) {
throw runtime_error("Value not found");
}

current->prev->next = current->next;
current->next->prev = current->prev;
delete current;
size--;
}

// 查找节点(返回索引,未找到返回-1)
int search(const T& value) const {
DNode<T>* current = head;
size_t index = 0;
while (current != nullptr) {
if (current->data == value) {
return static_cast<int>(index);
}
current = current->next;
index++;
}
return -1;
}

// 打印链表(从头到尾)
void printForward() const {
if (head == nullptr) {
cout << "List is empty" << endl;
return;
}
DNode<T>* current = head;
while (current != nullptr) {
cout << current->data << " <-> ";
current = current->next;
}
cout << "nullptr" << endl;
}

// 打印链表(从尾到头)
void printBackward() const {
if (tail == nullptr) {
cout << "List is empty" << endl;
return;
}
DNode<T>* current = tail;
while (current != nullptr) {
cout << current->data << " <-> ";
current = current->prev;
}
cout << "nullptr" << endl;
}

// 清空链表
void clear() {
while (head != nullptr) {
DNode<T>* temp = head;
head = head->next;
delete temp;
}
tail = nullptr;
size = 0;
}

// 获取链表大小
size_t getSize() const {
return size;
}

// 检查链表是否为空
bool isEmpty() const {
return size == 0;
}
};

2.2.3 循环链表的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 循环链表模板类
template <typename T>
class CircularLinkedList {
private:
Node<T>* head;
size_t size;
public:
CircularLinkedList() : head(nullptr), size(0) {}

~CircularLinkedList() {
clear();
}

// 插入节点到头部
void insertAtHead(const T& value) {
Node<T>* newNode = new Node<T>(value);
if (head == nullptr) {
newNode->next = newNode;
head = newNode;
} else {
Node<T>* tail = head;
while (tail->next != head) {
tail = tail->next;
}
newNode->next = head;
tail->next = newNode;
head = newNode;
}
size++;
}

// 插入节点到尾部
void insertAtTail(const T& value) {
Node<T>* newNode = new Node<T>(value);
if (head == nullptr) {
newNode->next = newNode;
head = newNode;
} else {
Node<T>* tail = head;
while (tail->next != head) {
tail = tail->next;
}
newNode->next = head;
tail->next = newNode;
}
size++;
}

// 删除头部节点
void deleteAtHead() {
if (head == nullptr) {
throw runtime_error("List is empty");
}
if (head->next == head) {
delete head;
head = nullptr;
} else {
Node<T>* tail = head;
while (tail->next != head) {
tail = tail->next;
}
Node<T>* temp = head;
head = head->next;
tail->next = head;
delete temp;
}
size--;
}

// 删除尾部节点
void deleteAtTail() {
if (head == nullptr) {
throw runtime_error("List is empty");
}
if (head->next == head) {
delete head;
head = nullptr;
} else {
Node<T>* prev = nullptr;
Node<T>* current = head;
while (current->next != head) {
prev = current;
current = current->next;
}
prev->next = head;
delete current;
}
size--;
}

// 打印链表
void print() const {
if (head == nullptr) {
cout << "List is empty" << endl;
return;
}
Node<T>* current = head;
do {
cout << current->data << " -> ";
current = current->next;
} while (current != head);
cout << "head" << endl;
}

// 清空链表
void clear() {
if (head == nullptr) {
return;
}
Node<T>* current = head->next;
while (current != head) {
Node<T>* temp = current;
current = current->next;
delete temp;
}
delete head;
head = nullptr;
size = 0;
}

// 获取链表大小
size_t getSize() const {
return size;
}

// 检查链表是否为空
bool isEmpty() const {
return size == 0;
}
};

2.2.4 链表的性能优化

内存管理优化
  1. 内存池

    • 为链表节点预分配内存池,减少频繁的动态内存分配
    • 实现自定义分配器,提高内存分配效率
  2. 节点复用

    • 实现空闲节点列表,复用被删除的节点
    • 减少内存分配和释放的开销
  3. 内存对齐

    • 确保链表节点按硬件要求对齐,提高访问速度
    • 使用alignas关键字或编译器指令
访问性能优化
  1. 缓存优化

    • 链表节点在内存中不连续,缓存局部性差
    • 考虑使用数组模拟链表,提高缓存命中率
  2. 遍历优化

    • 对于长链表,考虑使用跳跃链表(Skip List)
    • 实现双向遍历,根据位置选择从头部或尾部开始
  3. 分支预测

    • 减少链表操作中的分支,提高分支预测准确率
    • 使用条件移动指令代替分支指令
C++特有的优化
  1. 移动语义

    • 实现移动构造函数和移动赋值运算符
    • 减少节点创建和插入时的拷贝开销
  2. 智能指针

    • 使用std::unique_ptr管理节点内存,避免内存泄漏
    • 注意循环引用问题,必要时使用std::weak_ptr
  3. 模板特化

    • 为常见类型提供模板特化,提高性能
    • 例如,为内置类型优化内存布局
  4. 内联函数

    • 将频繁调用的链表操作声明为inline
    • 减少函数调用开销
应用场景优化
  1. 选择合适的链表类型

    • 单链表:插入删除操作频繁,只需单向遍历
    • 双链表:需要双向遍历,频繁在中间插入删除
    • 循环链表:需要环形操作,如约瑟夫问题
  2. 结合其他数据结构

    • 链表 + 哈希表:提高查找性能
    • 链表 + 二叉搜索树:实现有序链表
  3. 批量操作

    • 实现批量插入和删除操作
    • 减少遍历次数,提高效率

2.2.5 链表与数组的比较

操作数组单链表双链表
随机访问O(1)O(n)O(n)
头部插入O(n)O(1)O(1)
尾部插入O(1) (均摊)O(n)O(1)
中间插入O(n)O(1) (已知前驱)O(1) (已知位置)
头部删除O(n)O(1)O(1)
尾部删除O(1)O(n)O(1)
中间删除O(n)O(1) (已知前驱)O(1) (已知位置)
内存开销中 (额外指针)高 (两个额外指针)
缓存局部性

选择建议

  • 当需要频繁随机访问时,选择数组
  • 当需要频繁插入删除且位置不确定时,选择链表
  • 当内存空间有限时,选择数组
  • 当需要在两端频繁操作时,选择双链表

2.3 栈(Stack)

  • 定义:后进先出(LIFO)的数据结构,仅允许在栈顶进行插入(push)和删除(pop)操作。
  • C++实现:可使用 vector 或链表实现。
  • 应用场景:函数调用栈、表达式求值、括号匹配、深度优先搜索等。

2.3.1 栈的C++实现

基于vector的栈实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 基于vector的栈模板类
template <typename T, typename Container = std::vector<T>>
class Stack {
private:
Container c;
public:
// 检查栈是否为空
bool empty() const {
return c.empty();
}

// 获取栈的大小
size_t size() const {
return c.size();
}

// 获取栈顶元素
T& top() {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
return c.back();
}

const T& top() const {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
return c.back();
}

// 入栈
void push(const T& value) {
c.push_back(value);
}

// 移动语义版本的入栈
void push(T&& value) {
c.push_back(std::move(value));
}

// 变参模板版本的入栈(原地构造)
template <typename... Args>
void emplace(Args&&... args) {
c.emplace_back(std::forward<Args>(args)...);
}

// 出栈
void pop() {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
c.pop_back();
}

// 交换两个栈的内容
void swap(Stack& other) noexcept {
using std::swap;
swap(c, other.c);
}
};

// 非成员交换函数
template <typename T, typename Container>
void swap(Stack<T, Container>& lhs, Stack<T, Container>& rhs) noexcept {
lhs.swap(rhs);
}
基于链表的栈实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 基于链表的栈模板类
template <typename T>
class LinkedStack {
private:
struct Node {
T data;
Node* next;

Node(const T& value) : data(value), next(nullptr) {}
Node(T&& value) : data(std::move(value)), next(nullptr) {}
};

Node* topNode;
size_t size;
public:
LinkedStack() : topNode(nullptr), size(0) {}

~LinkedStack() {
clear();
}

// 检查栈是否为空
bool empty() const {
return size == 0;
}

// 获取栈的大小
size_t size() const {
return size;
}

// 获取栈顶元素
T& top() {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
return topNode->data;
}

const T& top() const {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
return topNode->data;
}

// 入栈
void push(const T& value) {
Node* newNode = new Node(value);
newNode->next = topNode;
topNode = newNode;
size++;
}

// 移动语义版本的入栈
void push(T&& value) {
Node* newNode = new Node(std::move(value));
newNode->next = topNode;
topNode = newNode;
size++;
}

// 出栈
void pop() {
if (empty()) {
throw std::underflow_error("Stack is empty");
}
Node* temp = topNode;
topNode = topNode->next;
delete temp;
size--;
}

// 清空栈
void clear() {
while (!empty()) {
pop();
}
}
};

2.3.2 栈的性能优化

内存管理优化
  1. 预分配内存

    • 对于基于vector的栈,使用reserve()预分配足够的空间
    • 减少频繁扩容的开销
  2. 内存池

    • 对于基于链表的栈,实现内存池管理节点内存
    • 减少动态内存分配的开销
  3. 移动语义

    • 利用C++11的移动语义,避免不必要的拷贝
    • 对于大对象,使用std::move()提高性能
操作优化
  1. 原地构造

    • 使用emplace()方法原地构造元素,避免中间拷贝
    • 减少构造和拷贝的开销
  2. 异常安全性

    • 提供强异常安全保证,确保栈操作的原子性
    • 避免在异常发生时栈处于不一致状态
  3. 内联函数

    • 将频繁调用的栈操作声明为inline
    • 减少函数调用开销
线程安全性
  1. 无锁设计

    • 对于并发场景,考虑使用无锁栈实现
    • 提高多线程环境下的性能
  2. 原子操作

    • 使用std::atomic实现线程安全的栈操作
    • 避免使用重量级的互斥锁

2.3.3 栈的应用案例

表达式求值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// 计算中缀表达式的值
int evaluateExpression(const std::string& expr) {
Stack<int> values;
Stack<char> operators;

for (size_t i = 0; i < expr.size(); i++) {
char c = expr[i];

// 跳过空格
if (std::isspace(c)) {
continue;
}

// 如果是数字,解析完整数字
if (std::isdigit(c)) {
int num = 0;
while (i < expr.size() && std::isdigit(expr[i])) {
num = num * 10 + (expr[i] - '0');
i++;
}
values.push(num);
i--; // 回退一位,因为循环会递增
}

// 如果是左括号,入栈
else if (c == '(') {
operators.push(c);
}

// 如果是右括号,计算直到左括号
else if (c == ')') {
while (!operators.empty() && operators.top() != '(') {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = operators.top();
operators.pop();

switch (op) {
case '+': values.push(val1 + val2); break;
case '-': values.push(val1 - val2); break;
case '*': values.push(val1 * val2); break;
case '/': values.push(val1 / val2); break;
}
}

// 弹出左括号
if (!operators.empty()) {
operators.pop();
}
}

// 如果是运算符
else if (c == '+' || c == '-' || c == '*' || c == '/') {
// 弹出优先级更高或相等的运算符并计算
while (!operators.empty() && hasHigherPrecedence(operators.top(), c)) {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = operators.top();
operators.pop();

switch (op) {
case '+': values.push(val1 + val2); break;
case '-': values.push(val1 - val2); break;
case '*': values.push(val1 * val2); break;
case '/': values.push(val1 / val2); break;
}
}

// 将当前运算符入栈
operators.push(c);
}
}

// 计算剩余的运算符
while (!operators.empty()) {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = operators.top();
operators.pop();

switch (op) {
case '+': values.push(val1 + val2); break;
case '-': values.push(val1 - val2); break;
case '*': values.push(val1 * val2); break;
case '/': values.push(val1 / val2); break;
}
}

// 栈顶元素就是结果
return values.top();
}

// 判断运算符优先级
bool hasHigherPrecedence(char op1, char op2) {
if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) {
return true;
}
return false;
}
括号匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 检查括号是否匹配
bool isBalanced(const std::string& expr) {
Stack<char> stack;

for (char c : expr) {
// 左括号入栈
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
continue;
}

// 右括号
if (c == ')' || c == '}' || c == ']') {
if (stack.empty()) {
return false;
}

char top = stack.top();
stack.pop();

if ((c == ')' && top != '(') ||
(c == '}' && top != '{') ||
(c == ']' && top != '[')) {
return false;
}
}
}

// 栈为空表示所有括号都匹配
return stack.empty();
}

2.3.4 栈与其他数据结构的比较

数据结构插入操作删除操作随机访问应用场景
O(1)O(1)O(n)函数调用、表达式求值
队列O(1)O(1)O(n)任务调度、消息队列
数组O(1) (尾部)O(1) (尾部)O(1)随机访问频繁
链表O(1) (头部)O(1) (头部)O(n)插入删除频繁

2.3.5 现代C++中的栈

std::stack

C++标准库提供了std::stack容器适配器,它可以基于不同的底层容器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stack>
#include <vector>
#include <deque>
#include <list>

// 基于deque的栈(默认)
std::stack<int> stack1;

// 基于vector的栈
std::stack<int, std::vector<int>> stack2;

// 基于list的栈
std::stack<int, std::list<int>> stack3;
性能特点
  • 基于deque:默认实现,兼顾了随机访问和插入删除性能
  • 基于vector:随机访问性能好,但扩容时可能需要拷贝数据
  • 基于list:插入删除性能稳定,但内存开销较大
扩展功能
  1. 最大容量限制

    • 实现带容量限制的栈,防止栈溢出
    • 适用于资源受限的环境
  2. 迭代器支持

    • 为栈添加迭代器支持,方便遍历栈中的元素
    • 扩展栈的使用场景
  3. 序列化支持

    • 实现栈的序列化和反序列化
    • 支持栈的持久化存储

2.4 队列(Queue)

  • 定义:先进先出(FIFO)的数据结构,仅允许在队尾插入(enqueue)和队头删除(dequeue)操作。
  • C++实现:可使用 deque 或链表实现。
  • 应用场景:任务调度、消息队列、广度优先搜索等。

2.4.1 队列的C++实现

基于deque的队列实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 基于deque的队列模板类
template <typename T, typename Container = std::deque<T>>
class Queue {
private:
Container c;
public:
// 检查队列是否为空
bool empty() const {
return c.empty();
}

// 获取队列的大小
size_t size() const {
return c.size();
}

// 获取队头元素
T& front() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return c.front();
}

const T& front() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return c.front();
}

// 获取队尾元素
T& back() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return c.back();
}

const T& back() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return c.back();
}

// 入队
void push(const T& value) {
c.push_back(value);
}

// 移动语义版本的入队
void push(T&& value) {
c.push_back(std::move(value));
}

// 变参模板版本的入队(原地构造)
template <typename... Args>
void emplace(Args&&... args) {
c.emplace_back(std::forward<Args>(args)...);
}

// 出队
void pop() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
c.pop_front();
}

// 交换两个队列的内容
void swap(Queue& other) noexcept {
using std::swap;
swap(c, other.c);
}
};

// 非成员交换函数
template <typename T, typename Container>
void swap(Queue<T, Container>& lhs, Queue<T, Container>& rhs) noexcept {
lhs.swap(rhs);
}
基于链表的队列实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 基于链表的队列模板类
template <typename T>
class LinkedQueue {
private:
struct Node {
T data;
Node* next;

Node(const T& value) : data(value), next(nullptr) {}
Node(T&& value) : data(std::move(value)), next(nullptr) {}
};

Node* frontNode;
Node* rearNode;
size_t size;
public:
LinkedQueue() : frontNode(nullptr), rearNode(nullptr), size(0) {}

~LinkedQueue() {
clear();
}

// 检查队列是否为空
bool empty() const {
return size == 0;
}

// 获取队列的大小
size_t size() const {
return size;
}

// 获取队头元素
T& front() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return frontNode->data;
}

const T& front() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return frontNode->data;
}

// 获取队尾元素
T& back() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return rearNode->data;
}

const T& back() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return rearNode->data;
}

// 入队
void push(const T& value) {
Node* newNode = new Node(value);
if (empty()) {
frontNode = newNode;
rearNode = newNode;
} else {
rearNode->next = newNode;
rearNode = newNode;
}
size++;
}

// 移动语义版本的入队
void push(T&& value) {
Node* newNode = new Node(std::move(value));
if (empty()) {
frontNode = newNode;
rearNode = newNode;
} else {
rearNode->next = newNode;
rearNode = newNode;
}
size++;
}

// 出队
void pop() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
Node* temp = frontNode;
frontNode = frontNode->next;
if (frontNode == nullptr) {
rearNode = nullptr;
}
delete temp;
size--;
}

// 清空队列
void clear() {
while (!empty()) {
pop();
}
}
};

2.4.2 队列的性能优化

内存管理优化
  1. 预分配内存

    • 对于基于deque的队列,预分配足够的空间
    • 减少频繁扩容的开销
  2. 内存池

    • 对于基于链表的队列,实现内存池管理节点内存
    • 减少动态内存分配的开销
  3. 移动语义

    • 利用C++11的移动语义,避免不必要的拷贝
    • 对于大对象,使用std::move()提高性能
操作优化
  1. 原地构造

    • 使用emplace()方法原地构造元素,避免中间拷贝
    • 减少构造和拷贝的开销
  2. 批量操作

    • 实现批量入队和出队操作
    • 减少函数调用开销,提高效率
  3. 异常安全性

    • 提供强异常安全保证,确保队列操作的原子性
    • 避免在异常发生时队列处于不一致状态

2.4.3 队列的应用案例

广度优先搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 图的广度优先搜索
void bfs(const std::vector<std::vector<int>>& graph, int start) {
std::vector<bool> visited(graph.size(), false);
Queue<int> queue;

visited[start] = true;
queue.push(start);

while (!queue.empty()) {
int current = queue.front();
queue.pop();
std::cout << current << " ";

for (int neighbor : graph[current]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.push(neighbor);
}
}
}
std::cout << std::endl;
}
任务调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 简单的任务调度器
class TaskScheduler {
private:
Queue<std::function<void()>> taskQueue;
std::mutex mutex;
std::condition_variable cv;
bool running;
std::thread workerThread;
public:
TaskScheduler() : running(true) {
workerThread = std::thread([this]() {
while (running) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [this]() { return !taskQueue.empty() || !running; });

if (!running && taskQueue.empty()) {
return;
}

task = std::move(taskQueue.front());
taskQueue.pop();
}

// 执行任务
task();
}
});
}

~TaskScheduler() {
{
std::unique_lock<std::mutex> lock(mutex);
running = false;
}
cv.notify_one();
workerThread.join();
}

// 添加任务
void addTask(std::function<void()> task) {
std::unique_lock<std::mutex> lock(mutex);
taskQueue.push(std::move(task));
cv.notify_one();
}
};

2.4.4 特殊类型的队列

优先队列

优先队列是一种特殊的队列,其中元素按照优先级排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <queue>
#include <vector>
#include <functional>

// 最大堆(默认)
std::priority_queue<int> maxHeap;

// 最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

// 自定义类型的优先队列
struct Task {
int priority;
std::string name;

bool operator<(const Task& other) const {
return priority < other.priority; // 最大堆
}
};

std::priority_queue<Task> taskQueue;
双端队列

双端队列允许在两端进行插入和删除操作:

1
2
3
4
5
6
7
#include <deque>

std::deque<int> dq;
dq.push_back(1); // 尾部插入
dq.push_front(2); // 头部插入
dq.pop_back(); // 尾部删除
dq.pop_front(); // 头部删除
循环队列

循环队列是一种使用固定大小数组实现的队列,通过模运算实现循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 循环队列模板类
template <typename T, size_t Capacity>
class CircularQueue {
private:
T data[Capacity];
size_t front;
size_t rear;
size_t size;
public:
CircularQueue() : front(0), rear(0), size(0) {}

// 检查队列是否为空
bool empty() const {
return size == 0;
}

// 检查队列是否已满
bool full() const {
return size == Capacity;
}

// 获取队列的大小
size_t getSize() const {
return size;
}

// 获取队头元素
T& front() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return data[front];
}

const T& front() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return data[front];
}

// 入队
void push(const T& value) {
if (full()) {
throw std::overflow_error("Queue is full");
}
data[rear] = value;
rear = (rear + 1) % Capacity;
size++;
}

// 出队
void pop() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
front = (front + 1) % Capacity;
size--;
}
};

2.4.5 队列的性能分析

队列类型插入操作删除操作随机访问内存开销适用场景
基于deque的队列O(1)O(1)O(n)一般场景
基于链表的队列O(1)O(1)O(n)频繁插入删除
循环队列O(1)O(1)O(1)固定大小场景
优先队列O(log n)O(log n)O(1)优先级调度

2.5 总结

线性表是最基础的数据结构,包括数组、链表、栈和队列等。在C++中,我们可以根据具体的应用场景选择合适的实现方式:

  1. 数组:适用于需要频繁随机访问的场景,具有良好的缓存局部性。
  2. 链表:适用于需要频繁插入删除的场景,具有灵活的内存管理。
  3. :适用于后进先出的场景,如函数调用、表达式求值。
  4. 队列:适用于先进先出的场景,如任务调度、广度优先搜索。

通过合理选择和优化这些数据结构,我们可以显著提高程序的性能和可靠性。在实际应用中,我们还可以结合这些基础数据结构,构建更复杂的数据结构,如树、图等。

template
class Stack {
private:
vectorelements;
public:
// 入栈
void push(const T& value) {
elements.push_back(value);
}

// 出栈
void pop() {
    if (isEmpty()) {
        throw runtime_error("Stack underflow");
    }
    elements.pop_back();
}

// 获取栈顶元素
T& top() {
    if (isEmpty()) {
        throw runtime_error("Stack is empty");
    }
    return elements.back();
}

// 检查栈是否为空
bool isEmpty() const {
    return elements.empty();
}

// 获取栈大小
size_t size() const {
    return elements.size();
}

// 打印栈
void print() const {
    if (isEmpty()) {
        cout << "Stack is empty" << endl;
        return;
    }
    cout << "Stack: ";
    for (const auto& elem : elements) {
        cout << elem << " ";
    }
    cout << endl;
}

};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

### 2.4 队列(Queue)
- **定义**:先进先出(FIFO)的数据结构,允许在队尾插入(enqueue),在队头删除(dequeue)。
- **C++实现**:可使用 `vector`、链表或STL的 `queue` 实现。
- **应用场景**:广度优先搜索(BFS)、任务调度、打印机队列、缓冲区管理。

#### 循环队列的C++实现
```cpp
template <typename T>
class CircularQueue {
private:
vector<T> elements;
size_t front; // 队头索引
size_t rear; // 队尾索引(下一个插入位置)
size_t capacity;
size_t size;
public:
CircularQueue(size_t cap) : capacity(cap), front(0), rear(0), size(0) {
elements.resize(capacity);
}

// 入队
void enqueue(const T& value) {
if (isFull()) {
throw runtime_error("Queue overflow");
}
elements[rear] = value;
rear = (rear + 1) % capacity;
size++;
}

// 出队
void dequeue() {
if (isEmpty()) {
throw runtime_error("Queue underflow");
}
front = (front + 1) % capacity;
size--;
}

// 获取队头元素
T& getFront() {
if (isEmpty()) {
throw runtime_error("Queue is empty");
}
return elements[front];
}

// 检查队列是否为空
bool isEmpty() const {
return size == 0;
}

// 检查队列是否已满
bool isFull() const {
return size == capacity;
}

// 获取队列大小
size_t getSize() const {
return size;
}

// 打印队列
void print() const {
if (isEmpty()) {
cout << "Queue is empty" << endl;
return;
}
cout << "Queue: ";
for (size_t i = 0; i < size; i++) {
size_t index = (front + i) % capacity;
cout << elements[index] << " ";
}
cout << endl;
}
};

3. 树结构

3.1 二叉树

  • 定义:每个节点最多有两个子节点(左子树和右子树)的树结构。
  • 遍历方式:前序(根→左→右)、中序(左→根→右)、后序(左→右→根)、层序(BFS)。

二叉树的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
template <typename T>
class TreeNode {
public:
T data;
TreeNode<T>* left;
TreeNode<T>* right;

TreeNode(const T& value) : data(value), left(nullptr), right(nullptr) {}
};

template <typename T>
class BinaryTree {
private:
TreeNode<T>* root;

// 递归销毁树
void destroyTree(TreeNode<T>* node) {
if (node != nullptr) {
destroyTree(node->left);
destroyTree(node->right);
delete node;
}
}

// 前序遍历(递归)
void preorderTraversal(TreeNode<T>* node) const {
if (node != nullptr) {
cout << node->data << " ";
preorderTraversal(node->left);
preorderTraversal(node->right);
}
}

// 中序遍历(递归)
void inorderTraversal(TreeNode<T>* node) const {
if (node != nullptr) {
inorderTraversal(node->left);
cout << node->data << " ";
inorderTraversal(node->right);
}
}

// 后序遍历(递归)
void postorderTraversal(TreeNode<T>* node) const {
if (node != nullptr) {
postorderTraversal(node->left);
postorderTraversal(node->right);
cout << node->data << " ";
}
}
public:
BinaryTree() : root(nullptr) {}

~BinaryTree() {
destroyTree(root);
}

// 设置根节点
void setRoot(TreeNode<T>* node) {
root = node;
}

// 获取根节点
TreeNode<T>* getRoot() const {
return root;
}

// 前序遍历
void preorder() const {
cout << "Preorder traversal: ";
preorderTraversal(root);
cout << endl;
}

// 中序遍历
void inorder() const {
cout << "Inorder traversal: ";
inorderTraversal(root);
cout << endl;
}

// 后序遍历
void postorder() const {
cout << "Postorder traversal: ";
postorderTraversal(root);
cout << endl;
}

// 层序遍历(使用队列)
void levelOrder() const {
if (root == nullptr) {
cout << "Tree is empty" << endl;
return;
}
cout << "Level order traversal: ";
queue<TreeNode<T>*> q;
q.push(root);
while (!q.empty()) {
TreeNode<T>* current = q.front();
q.pop();
cout << current->data << " ";
if (current->left != nullptr) {
q.push(current->left);
}
if (current->right != nullptr) {
q.push(current->right);
}
}
cout << endl;
}
};

3.2 二叉搜索树(BST)

  • 性质:左子树所有节点值小于根节点,右子树所有节点值大于根节点(无重复值)。
  • 操作:查找、插入、删除,时间复杂度平均 O(og n) ,最坏 O(n) (退化为链表)。

二叉搜索树的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
template <typename T>
class BST {
private:
TreeNode<T>* root;

// 插入辅助函数
TreeNode<T>* insertHelper(TreeNode<T>* node, const T& value) {
if (node == nullptr) {
return new TreeNode<T>(value);
}
if (value < node->data) {
node->left = insertHelper(node->left, value);
} else if (value > node->data) {
node->right = insertHelper(node->right, value);
}
// 忽略重复值
return node;
}

// 查找辅助函数
TreeNode<T>* searchHelper(TreeNode<T>* node, const T& value) const {
if (node == nullptr || node->data == value) {
return node;
}
if (value < node->data) {
return searchHelper(node->left, value);
} else {
return searchHelper(node->right, value);
}
}

// 查找最小节点
TreeNode<T>* findMin(TreeNode<T>* node) const {
while (node->left != nullptr) {
node = node->left;
}
return node;
}

// 删除辅助函数
TreeNode<T>* deleteHelper(TreeNode<T>* node, const T& value) {
if (node == nullptr) {
return node;
}
if (value < node->data) {
node->left = deleteHelper(node->left, value);
} else if (value > node->data) {
node->right = deleteHelper(node->right, value);
} else {
// 情况1:叶子节点
if (node->left == nullptr && node->right == nullptr) {
delete node;
return nullptr;
}
// 情况2:仅有一个子节点
else if (node->left == nullptr) {
TreeNode<T>* temp = node->right;
delete node;
return temp;
} else if (node->right == nullptr) {
TreeNode<T>* temp = node->left;
delete node;
return temp;
}
// 情况3:有两个子节点
TreeNode<T>* temp = findMin(node->right);
node->data = temp->data;
node->right = deleteHelper(node->right, temp->data);
}
return node;
}

// 中序遍历辅助函数
void inorderHelper(TreeNode<T>* node) const {
if (node != nullptr) {
inorderHelper(node->left);
cout << node->data << " ";
inorderHelper(node->right);
}
}

// 销毁树
void destroyTree(TreeNode<T>* node) {
if (node != nullptr) {
destroyTree(node->left);
destroyTree(node->right);
delete node;
}
}
public:
BST() : root(nullptr) {}

~BST() {
destroyTree(root);
}

// 插入节点
void insert(const T& value) {
root = insertHelper(root, value);
}

// 查找节点
bool search(const T& value) const {
return searchHelper(root, value) != nullptr;
}

// 删除节点
void remove(const T& value) {
root = deleteHelper(root, value);
}

// 中序遍历(BST的中序遍历结果为有序序列)
void inorder() const {
cout << "Inorder traversal: ";
inorderHelper(root);
cout << endl;
}
};

3.3 平衡树(AVL树)

  • 定义:左右子树高度差(平衡因子)的绝对值不超过1的二叉搜索树。
  • 平衡因子:左子树高度 - 右子树高度(范围:-1, 0, 1)。
  • 平衡调整:通过旋转操作(左旋、右旋、左右旋、右左旋)维持平衡。

AVL树的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
template <typename T>
class AVLNode {
public:
T data;
AVLNode<T>* left;
AVLNode<T>* right;
int height; // 节点高度

AVLNode(const T& value) : data(value), left(nullptr), right(nullptr), height(1) {}
};

template <typename T>
class AVLTree {
private:
AVLNode<T>* root;

// 获取节点高度
int getHeight(AVLNode<T>* node) const {
if (node == nullptr) {
return 0;
}
return node->height;
}

// 计算平衡因子
int getBalanceFactor(AVLNode<T>* node) const {
if (node == nullptr) {
return 0;
}
return getHeight(node->left) - getHeight(node->right);
}

// 更新节点高度
void updateHeight(AVLNode<T>* node) {
if (node == nullptr) {
return;
}
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
}

// 右旋
AVLNode<T>* rightRotate(AVLNode<T>* y) {
AVLNode<T>* x = y->left;
AVLNode<T>* T2 = x->right;

// 执行旋转
x->right = y;
y->left = T2;

// 更新高度
updateHeight(y);
updateHeight(x);

return x;
}

// 左旋
AVLNode<T>* leftRotate(AVLNode<T>* x) {
AVLNode<T>* y = x->right;
AVLNode<T>* T2 = y->left;

// 执行旋转
y->left = x;
x->right = T2;

// 更新高度
updateHeight(x);
updateHeight(y);

return y;
}

// 插入辅助函数
AVLNode<T>* insertHelper(AVLNode<T>* node, const T& value) {
// 1. 执行标准BST插入
if (node == nullptr) {
return new AVLNode<T>(value);
}
if (value < node->data) {
node->left = insertHelper(node->left, value);
} else if (value > node->data) {
node->right = insertHelper(node->right, value);
} else {
return node; // 忽略重复值
}

// 2. 更新节点高度
updateHeight(node);

// 3. 计算平衡因子
int balance = getBalanceFactor(node);

// 4. 根据平衡因子调整
// LL型:右旋
if (balance > 1 && value < node->left->data) {
return rightRotate(node);
}
// RR型:左旋
if (balance < -1 && value > node->right->data) {
return leftRotate(node);
}
// LR型:先左旋左子树,再右旋根节点
if (balance > 1 && value > node->left->data) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// RL型:先右旋右子树,再左旋根节点
if (balance < -1 && value < node->right->data) {
node->right = rightRotate(node->right);
return leftRotate(node);
}

return node;
}

// 查找最小节点
AVLNode<T>* findMin(AVLNode<T>* node) const {
while (node->left != nullptr) {
node = node->left;
}
return node;
}

// 删除辅助函数
AVLNode<T>* deleteHelper(AVLNode<T>* node, const T& value) {
// 1. 执行标准BST删除
if (node == nullptr) {
return node;
}
if (value < node->data) {
node->left = deleteHelper(node->left, value);
} else if (value > node->data) {
node->right = deleteHelper(node->right, value);
} else {
// 情况1:叶子节点
if (node->left == nullptr && node->right == nullptr) {
delete node;
return nullptr;
}
// 情况2:仅有一个子节点
else if (node->left == nullptr) {
AVLNode<T>* temp = node->right;
delete node;
return temp;
} else if (node->right == nullptr) {
AVLNode<T>* temp = node->left;
delete node;
return temp;
}
// 情况3:有两个子节点
AVLNode<T>* temp = findMin(node->right);
node->data = temp->data;
node->right = deleteHelper(node->right, temp->data);
}

// 2. 更新节点高度
updateHeight(node);

// 3. 计算平衡因子
int balance = getBalanceFactor(node);

// 4. 根据平衡因子调整
// LL型:右旋
if (balance > 1 && getBalanceFactor(node->left) >= 0) {
return rightRotate(node);
}
// LR型:先左旋左子树,再右旋根节点
if (balance > 1 && getBalanceFactor(node->left) < 0) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// RR型:左旋
if (balance < -1 && getBalanceFactor(node->right) <= 0) {
return leftRotate(node);
}
// RL型:先右旋右子树,再左旋根节点
if (balance < -1 && getBalanceFactor(node->right) > 0) {
node->right = rightRotate(node->right);
return leftRotate(node);
}

return node;
}

// 中序遍历
void inorderHelper(AVLNode<T>* node) const {
if (node != nullptr) {
inorderHelper(node->left);
cout << node->data << " ";
inorderHelper(node->right);
}
}

// 销毁树
void destroyTree(AVLNode<T>* node) {
if (node != nullptr) {
destroyTree(node->left);
destroyTree(node->right);
delete node;
}
}
public:
AVLTree() : root(nullptr) {}

~AVLTree() {
destroyTree(root);
}

// 插入节点
void insert(const T& value) {
root = insertHelper(root, value);
}

// 删除节点
void remove(const T& value) {
root = deleteHelper(root, value);
}

// 中序遍历
void inorder() const {
cout << "Inorder traversal: ";
inorderHelper(root);
cout << endl;
}
};

3.4 红黑树

  • 性质
    1. 每个节点为红色或黑色。
    2. 根节点为黑色。
    3. 叶节点(NIL)为黑色。
    4. 红色节点的子节点为黑色。
    5. 从根到叶节点的黑色节点数相同(黑高相同)。
  • 操作:插入和删除时通过旋转和变色维持平衡,时间复杂度 O(og n) 。
  • 应用:C++ STL中的 mapsetmultimapmultiset

红黑树的C++实现(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
template <typename T>
class RBNode {
public:
T data;
RBNode<T>* left;
RBNode<T>* right;
RBNode<T>* parent;
bool isRed; // true为红色,false为黑色

RBNode(const T& value) : data(value), left(nullptr), right(nullptr), parent(nullptr), isRed(true) {}
};

template <typename T>
class RedBlackTree {
private:
RBNode<T>* root;
RBNode<T>* nil; // 哨兵节点(表示NIL)

// 左旋
void leftRotate(RBNode<T>* x) {
RBNode<T>* y = x->right;
x->right = y->left;
if (y->left != nil) {
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == nil) {
root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
y->left = x;
x->parent = y;
}

// 右旋
void rightRotate(RBNode<T>* y) {
RBNode<T>* x = y->left;
y->left = x->right;
if (x->right != nil) {
x->right->parent = y;
}
x->parent = y->parent;
if (y->parent == nil) {
root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
x->right = y;
y->parent = x;
}

// 插入修复
void insertFixup(RBNode<T>* z) {
while (z->parent->isRed) {
if (z->parent == z->parent->parent->left) {
RBNode<T>* y = z->parent->parent->right;
if (y->isRed) {
// 情况1:叔叔节点为红色
z->parent->isRed = false;
y->isRed = false;
z->parent->parent->isRed = true;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
// 情况2:叔叔节点为黑色,当前节点为右孩子
z = z->parent;
leftRotate(z);
}
// 情况3:叔叔节点为黑色,当前节点为左孩子
z->parent->isRed = false;
z->parent->parent->isRed = true;
rightRotate(z->parent->parent);
}
} else {
// 对称情况
RBNode<T>* y = z->parent->parent->left;
if (y->isRed) {
z->parent->isRed = false;
y->isRed = false;
z->parent->parent->isRed = true;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
rightRotate(z);
}
z->parent->isRed = false;
z->parent->parent->isRed = true;
leftRotate(z->parent->parent);
}
}
}
root->isRed = false;
}

// 插入节点
void insertNode(const T& value) {
RBNode<T>* z = new RBNode<T>(value);
RBNode<T>* y = nil;
RBNode<T>* x = root;

while (x != nil) {
y = x;
if (z->data < x->data) {
x = x->left;
} else if (z->data > x->data) {
x = x->right;
} else {
// 重复值,直接返回
delete z;
return;
}
}

z->parent = y;
if (y == nil) {
root = z;
} else if (z->data < y->data) {
y->left = z;
} else {
y->right = z;
}

z->left = nil;
z->right = nil;
insertFixup(z);
}

// 中序遍历
void inorderHelper(RBNode<T>* node) const {
if (node != nil) {
inorderHelper(node->left);
cout << node->data << (node->isRed ? "(R) " : "(B) ");
inorderHelper(node->right);
}
}

// 销毁树
void destroyTree(RBNode<T>* node) {
if (node != nil) {
destroyTree(node->left);
destroyTree(node->right);
if (node != this->nil) {
delete node;
}
}
}
public:
RedBlackTree() {
nil = new RBNode<T>(T());
nil->isRed = false;
root = nil;
}

~RedBlackTree() {
destroyTree(root);
delete nil;
}

// 插入节点
void insert(const T& value) {
insertNode(value);
}

// 中序遍历
void inorder() const {
cout << "Inorder traversal: ";
inorderHelper(root);
cout << endl;
}
};

4. 图结构

4.1 图的表示

  • 邻接矩阵:二维数组 adj[i][j] 表示顶点 i 到顶点 j 的边(有权值则存储权值,否则存储0/1)。适合稠密图,空间复杂度 O(V^2) 。
  • 邻接表:数组 adj,其中 adj[i] 是顶点 i 的邻接顶点链表。适合稀疏图,空间复杂度 O(V+E) 。

邻接表的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
template <typename T>
class Edge {
public:
int dest; // 目标顶点
T weight; // 边权

Edge(int d, const T& w) : dest(d), weight(w) {}
};

template <typename T>
class Graph {
private:
int V; // 顶点数
vector<vector<Edge<T>>> adj; // 邻接表
public:
Graph(int vertices) : V(vertices), adj(vertices) {}

// 添加边(有向图)
void addEdge(int src, int dest, const T& weight) {
adj[src].emplace_back(dest, weight);
}

// 添加边(无向图,双向添加)
void addUndirectedEdge(int src, int dest, const T& weight) {
addEdge(src, dest, weight);
addEdge(dest, src, weight);
}

// 获取顶点数
int getVertices() const {
return V;
}

// 获取邻接表
const vector<vector<Edge<T>>>& getAdj() const {
return adj;
}

// 打印图
void print() const {
for (int i = 0; i < V; i++) {
cout << "Vertex " << i << ": ";
for (const auto& edge : adj[i]) {
cout << "(" << edge.dest << ", " << edge.weight << ") -> ";
}
cout << "nullptr" << endl;
}
}
};

4.2 图的遍历

  • 深度优先搜索(DFS):递归或栈实现,探索路径到底后回溯。
  • 广度优先搜索(BFS):队列实现,按层访问节点。

DFS与BFS的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
template <typename T>
class GraphTraversal {
private:
const Graph<T>& graph;

// DFS辅助函数
void dfsHelper(int start, vector<bool>& visited) const {
visited[start] = true;
cout << start << " ";

const auto& adj = graph.getAdj();
for (const auto& edge : adj[start]) {
int dest = edge.dest;
if (!visited[dest]) {
dfsHelper(dest, visited);
}
}
}
public:
GraphTraversal(const Graph<T>& g) : graph(g) {}

// DFS遍历
void dfs(int start) const {
int V = graph.getVertices();
vector<bool> visited(V, false);
cout << "DFS traversal starting from " << start << ": ";
dfsHelper(start, visited);
cout << endl;
}

// BFS遍历
void bfs(int start) const {
int V = graph.getVertices();
vector<bool> visited(V, false);
queue<int> q;

visited[start] = true;
q.push(start);

cout << "BFS traversal starting from " << start << ": ";
while (!q.empty()) {
int current = q.front();
q.pop();
cout << current << " ";

const auto& adj = graph.getAdj();
for (const auto& edge : adj[current]) {
int dest = edge.dest;
if (!visited[dest]) {
visited[dest] = true;
q.push(dest);
}
}
}
cout << endl;
}
};

4.3 最短路径算法

  • Dijkstra算法:单源最短路径算法,适用于边权为正的图。使用优先队列优化,时间复杂度 O(E og V) 。
  • Floyd-Warshall算法:多源最短路径算法,使用动态规划实现,时间复杂度 O(V^3) 。

Dijkstra算法的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
template <typename T>
class Dijkstra {
private:
const Graph<T>& graph;
public:
Dijkstra(const Graph<T>& g) : graph(g) {}

// 计算从起点到所有顶点的最短路径
vector<T> shortestPath(int start) const {
int V = graph.getVertices();
vector<T> dist(V, numeric_limits<T>::max());
vector<bool> visited(V, false);

// 优先队列:存储(距离,顶点),按距离排序
priority_queue<pair<T, int>, vector<pair<T, int>>, greater<pair<T, int>>> pq;

// 起点距离为0
dist[start] = 0;
pq.emplace(0, start);

while (!pq.empty()) {
auto [currentDist, u] = pq.top();
pq.pop();

if (visited[u]) {
continue;
}
visited[u] = true;

const auto& adj = graph.getAdj();
for (const auto& edge : adj[u]) {
int v = edge.dest;
T weight = edge.weight;

if (!visited[v] && dist[u] != numeric_limits<T>::max() && dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
pq.emplace(dist[v], v);
}
}
}

return dist;
}

// 打印最短路径
void printShortestPath(int start) const {
vector<T> dist = shortestPath(start);
cout << "Shortest paths from " << start << ":" << endl;
for (int i = 0; i < dist.size(); i++) {
if (dist[i] == numeric_limits<T>::max()) {
cout << "Vertex " << i << ": unreachable" << endl;
} else {
cout << "Vertex " << i << ": " << dist[i] << endl;
}
}
}
};

4.4 最小生成树(MST)

  • Prim算法:从单个顶点开始,逐步添加连接树与非树顶点的最小边,时间复杂度 O(E og V) 。
  • Kruskal算法:按边权排序,依次添加边,使用并查集避免环,时间复杂度 O(E og E) 。

Kruskal算法的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
template <typename T>
class Kruskal {
private:
const Graph<T>& graph;

// 并查集结构
struct UnionFind {
vector<int> parent;
vector<int> rank;

UnionFind(int size) {
parent.resize(size);
rank.resize(size, 0);
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}

int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}

void unite(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);

if (xRoot == yRoot) {
return;
}

if (rank[xRoot] < rank[yRoot]) {
parent[xRoot] = yRoot;
} else if (rank[xRoot] > rank[yRoot]) {
parent[yRoot] = xRoot;
} else {
parent[yRoot] = xRoot;
rank[xRoot]++;
}
}
};
public:
Kruskal(const Graph<T>& g) : graph(g) {}

// 计算最小生成树
vector<pair<pair<int, int>, T>> computeMST() const {
int V = graph.getVertices();
const auto& adj = graph.getAdj();

// 收集所有边
vector<pair<T, pair<int, int>>> edges;
for (int u = 0; u < V; u++) {
for (const auto& edge : adj[u]) {
int v = edge.dest;
T weight = edge.weight;
edges.emplace_back(weight, make_pair(u, v));
}
}

// 按边权排序
sort(edges.begin(), edges.end());

UnionFind uf(V);
vector<pair<pair<int, int>, T>> mst;
T totalWeight = 0;

for (const auto& edge : edges) {
T weight = edge.first;
int u = edge.second.first;
int v = edge.second.second;

if (uf.find(u) != uf.find(v)) {
uf.unite(u, v);
mst.emplace_back(make_pair(u, v), weight);
totalWeight += weight;
}
}

// 打印MST
cout << "Minimum Spanning Tree edges:" << endl;
for (const auto& edge : mst) {
int u = edge.first.first;
int v = edge.first.second;
T weight = edge.second;
cout << u << " - " << v << " (weight: " << weight << ")" << endl;
}
cout << "Total weight of MST: " << totalWeight << endl;

return mst;
}
};

5. 排序算法

5.1 插入排序

  • 思想:将元素插入到已排序部分的正确位置,时间复杂度 O(n^2) ,适合小规模或基本有序数据。

插入排序的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
void insertionSort(vector<T>& arr) {
int n = arr.size();
for (int i = 1; i < n; i++) {
T key = arr[i];
int j = i - 1;
// 将大于key的元素向右移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}

5.2 归并排序

  • 思想:分治,将数组分为两半递归排序,再合并,时间复杂度 O(n og n) ,稳定排序。

归并排序的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
template <typename T>
void merge(vector<T>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;

// 创建临时数组
vector<T> L(n1);
vector<T> R(n2);

// 复制数据
for (int i = 0; i < n1; i++) {
L[i] = arr[left + i];
}
for (int j = 0; j < n2; j++) {
R[j] = arr[mid + 1 + j];
}

// 合并临时数组
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k++] = L[i++];
} else {
arr[k++] = R[j++];
}
}

// 复制剩余元素
while (i < n1) {
arr[k++] = L[i++];
}
while (j < n2) {
arr[k++] = R[j++];
}
}

template <typename T>
void mergeSort(vector<T>& arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
// 递归排序左半部分
mergeSort(arr, left, mid);
// 递归排序右半部分
mergeSort(arr, mid + 1, right);
// 合并
merge(arr, left, mid, right);
}
}

template <typename T>
void mergeSort(vector<T>& arr) {
mergeSort(arr, 0, arr.size() - 1);
}

5.3 快速排序

  • 思想:选择pivot划分数组为两部分,递归排序,时间复杂度平均 O(n og n) ,最坏 O(n^2) ,非稳定排序。

快速排序的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template <typename T>
int partition(vector<T>& arr, int low, int high) {
T pivot = arr[high]; // 选择最右端元素为pivot
int i = low - 1; // 小于pivot的元素的边界

for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i + 1], arr[high]);
return i + 1;
}

template <typename T>
void quickSort(vector<T>& arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
// 递归排序左半部分
quickSort(arr, low, pi - 1);
// 递归排序右半部分
quickSort(arr, pi + 1, high);
}
}

template <typename T>
void quickSort(vector<T>& arr) {
quickSort(arr, 0, arr.size() - 1);
}

5.4 堆排序

  • 思想:利用大顶堆特性,依次取出堆顶元素,时间复杂度 O(n og n) ,非稳定排序。

堆排序的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
template <typename T>
void heapify(vector<T>& arr, int n, int i) {
int largest = i; // 初始化largest为根
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2;// 右子节点

// 如果左子节点大于根
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点大于当前largest
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果largest不是根
if (largest != i) {
swap(arr[i], arr[largest]);
// 递归调整受影响的子树
heapify(arr, n, largest);
}
}

template <typename T>
void heapSort(vector<T>& arr) {
int n = arr.size();

// 构建大顶堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}

// 依次取出堆顶元素
for (int i = n - 1; i > 0; i--) {
swap(arr[0], arr[i]); // 将堆顶(最大值)与堆末尾元素交换
heapify(arr, i, 0); // 对剩余堆进行调整
}
}

5.5 排序算法比较

排序算法最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度稳定性
插入排序O(n)O(n^2)O(n^2)O(1)稳定
归并排序O(n og n)O(n og n)O(n og n)O(n)稳定
快速排序O(n og n)O(n^2)O(n og n)O(og n)不稳定
堆排序O(n og n)O(n og n)O(n og n)O(1)不稳定
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定

6. 查找算法

6.1 二分查找

  • 条件:有序数组,时间复杂度 O(og n) 。

二分查找的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
template <typename T>
int binarySearch(const vector<T>& arr, const T& target) {
int left = 0;
int right = arr.size() - 1;

while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (arr[mid] == target) {
return mid; // 找到目标
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}

return -1; // 未找到
}

// 递归实现
template <typename T>
int binarySearchRecursive(const vector<T>& arr, int left, int right, const T& target) {
if (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursive(arr, mid + 1, right, target);
} else {
return binarySearchRecursive(arr, left, mid - 1, target);
}
}
return -1;
}

template <typename T>
int binarySearchRecursive(const vector<T>& arr, const T& target) {
return binarySearchRecursive(arr, 0, arr.size() - 1, target);
}

6.2 哈希表

  • 思想:使用哈希函数将键映射到哈希表索引,时间复杂度平均 O(1) ,最坏 O(n) 。
  • 冲突处理:链地址法、开放定址法。

哈希表的C++实现(链地址法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
template <typename K, typename V>
class HashNode {
public:
K key;
V value;
HashNode<K, V>* next;

HashNode(const K& k, const V& v) : key(k), value(v), next(nullptr) {}
};

template <typename K, typename V>
class HashTable {
private:
vector<HashNode<K, V>*> table;
size_t size;
size_t capacity;

// 哈希函数
size_t hash(const K& key) const {
// 简单的哈希函数,实际应用中应使用更复杂的哈希函数
return hash<K>()(key) % capacity;
}

// 扩容
void resize() {
size_t newCapacity = capacity * 2;
vector<HashNode<K, V>*> newTable(newCapacity, nullptr);

// 重新哈希所有元素
for (size_t i = 0; i < capacity; i++) {
HashNode<K, V>* current = table[i];
while (current != nullptr) {
HashNode<K, V>* next = current->next;
size_t index = hash<K>()(current->key) % newCapacity;
current->next = newTable[index];
newTable[index] = current;
current = next;
}
}

table.swap(newTable);
capacity = newCapacity;
}
public:
HashTable(size_t initialCapacity = 16) : size(0), capacity(initialCapacity) {
table.resize(capacity, nullptr);
}

~HashTable() {
clear();
}

// 插入键值对
void insert(const K& key, const V& value) {
// 检查负载因子,超过0.7则扩容
if (size >= capacity * 0.7) {
resize();
}

size_t index = hash(key);
HashNode<K, V>* current = table[index];

// 检查是否已存在该键
while (current != nullptr) {
if (current->key == key) {
current->value = value; // 更新值
return;
}
current = current->next;
}

// 插入新节点到链表头部
HashNode<K, V>* newNode = new HashNode<K, V>(key, value);
newNode->next = table[index];
table[index] = newNode;
size++;
}

// 查找值
V get(const K& key) const {
size_t index = hash(key);
HashNode<K, V>* current = table[index];

while (current != nullptr) {
if (current->key == key) {
return current->value;
}
current = current->next;
}

throw runtime_error("Key not found");
}

// 删除键值对
void remove(const K& key) {
size_t index = hash(key);
HashNode<K, V>* current = table[index];
HashNode<K, V>* prev = nullptr;

while (current != nullptr) {
if (current->key == key) {
if (prev == nullptr) {
table[index] = current->next;
} else {
prev->next = current->next;
}
delete current;
size--;
return;
}
prev = current;
current = current->next;
}

throw runtime_error("Key not found");
}

// 检查键是否存在
bool contains(const K& key) const {
size_t index = hash(key);
HashNode<K, V>* current = table[index];

while (current != nullptr) {
if (current->key == key) {
return true;
}
current = current->next;
}

return false;
}

// 清空哈希表
void clear() {
for (size_t i = 0; i < capacity; i++) {
HashNode<K, V>* current = table[i];
while (current != nullptr) {
HashNode<K, V>* temp = current;
current = current->next;
delete temp;
}
table[i] = nullptr;
}
size = 0;
}

// 获取哈希表大小
size_t getSize() const {
return size;
}

// 检查哈希表是否为空
bool isEmpty() const {
return size == 0;
}
};

7. 高级数据结构

7.1 B树

  • 定义:多路搜索树,每个节点包含多个键和子节点指针,适用于磁盘等外部存储。
  • 性质:根节点至少有2个子节点,非根节点至少有 m/2 个子节点( m 为阶数),所有叶节点在同一层。

B树的C++实现(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
template <typename T>
class BTreeNode {
public:
vector<T> keys; // 键值
vector<BTreeNode<T>*> children; // 子节点指针
bool isLeaf; // 是否为叶节点

BTreeNode(bool leaf) : isLeaf(leaf) {}
};

template <typename T>
class BTree {
private:
BTreeNode<T>* root;
int order; // B树的阶数

// 分裂子节点
void splitChild(BTreeNode<T>* parent, int index, BTreeNode<T>* child) {
BTreeNode<T>* newChild = new BTreeNode<T>(child->isLeaf);
int mid = order / 2;

// 复制后半部分键到新子节点
for (int i = mid + 1; i < child->keys.size(); i++) {
newChild->keys.push_back(child->keys[i]);
}

// 如果子节点不是叶节点,复制后半部分子节点指针
if (!child->isLeaf) {
for (int i = mid + 1; i < child->children.size(); i++) {
newChild->children.push_back(child->children[i]);
}
}

// 调整子节点的大小
child->keys.resize(mid);
if (!child->isLeaf) {
child->children.resize(mid + 1);
}

// 在父节点中插入中间键
parent->keys.insert(parent->keys.begin() + index, child->keys[mid]);

// 在父节点中插入新子节点指针
parent->children.insert(parent->children.begin() + index + 1, newChild);
}

// 插入辅助函数
void insertNonFull(BTreeNode<T>* node, const T& key) {
int i = node->keys.size() - 1;

if (node->isLeaf) {
// 如果是叶节点,直接插入键
node->keys.push_back(key);
while (i >= 0 && node->keys[i] > key) {
node->keys[i + 1] = node->keys[i];
i--;
}
node->keys[i + 1] = key;
} else {
// 如果不是叶节点,找到插入位置
while (i >= 0 && node->keys[i] > key) {
i--;
}
i++;

// 检查子节点是否已满
if (node->children[i]->keys.size() == order - 1) {
// 分裂子节点
splitChild(node, i, node->children[i]);
// 确定插入到分裂后的哪个子节点
if (key > node->keys[i]) {
i++;
}
}
insertNonFull(node->children[i], key);
}
}

// 搜索辅助函数
bool searchHelper(BTreeNode<T>* node, const T& key) const {
int i = 0;
while (i < node->keys.size() && key > node->keys[i]) {
i++;
}

if (i < node->keys.size() && key == node->keys[i]) {
return true;
}

if (node->isLeaf) {
return false;
}

return searchHelper(node->children[i], key);
}

// 销毁树
void destroyTree(BTreeNode<T>* node) {
if (node != nullptr) {
if (!node->isLeaf) {
for (auto child : node->children) {
destroyTree(child);
}
}
delete node;
}
}
public:
BTree(int ord) : order(ord), root(nullptr) {}

~BTree() {
destroyTree(root);
}

// 插入键
void insert(const T& key) {
if (root == nullptr) {
// 创建根节点
root = new BTreeNode<T>(true);
root->keys.push_back(key);
} else {
// 如果根节点已满,分裂根节点
if (root->keys.size() == order - 1) {
BTreeNode<T>* newRoot = new BTreeNode<T>(false);
newRoot->children.push_back(root);
splitChild(newRoot, 0, root);

// 确定插入到分裂后的哪个子节点
int i = 0;
if (key > newRoot->keys[0]) {
i++;
}
insertNonFull(newRoot->children[i], key);

root = newRoot;
} else {
insertNonFull(root, key);
}
}
}

// 搜索键
bool search(const T& key) const {
if (root == nullptr) {
return false;
}
return searchHelper(root, key);
}
};

7.2 B+树

  • 定义:B树变种,所有数据存储在叶节点,叶节点形成链表,适合范围查询。
  • 优势:叶节点链表支持顺序访问,查询效率更高,适合数据库索引。

7.3 斐波那契堆

  • 定义:由多个最小堆组成的森林,支持合并、插入、删除最小值等操作,均摊时间复杂度优于二叉堆。
  • 优势:插入和合并操作的均摊时间复杂度为 O(1) ,删除最小值为 O(og n) 。
  • 应用:图算法(如Prim和Dijkstra的优化)。

7.4 并查集

  • 定义:用于管理元素所属集合的数据结构,支持查找和合并操作。
  • 优化:路径压缩和按秩合并,使时间复杂度接近 O(1) 。
  • 应用:Kruskal最小生成树算法、连通分量问题。

并查集的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class UnionFind {
private:
vector<int> parent;
vector<int> rank;
public:
UnionFind(int size) {
parent.resize(size);
rank.resize(size, 0);
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}

// 查找根节点(路径压缩)
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}

// 合并两个集合(按秩合并)
void unite(int x, int y) {
int xRoot = find(x);
int yRoot = find(y);

if (xRoot == yRoot) {
return;
}

if (rank[xRoot] < rank[yRoot]) {
parent[xRoot] = yRoot;
} else if (rank[xRoot] > rank[yRoot]) {
parent[yRoot] = xRoot;
} else {
parent[yRoot] = xRoot;
rank[xRoot]++;
}
}

// 检查两个元素是否在同一集合
bool isConnected(int x, int y) {
return find(x) == find(y);
}
};

8. 算法设计技术

8.1 贪心算法

  • 策略:每一步选择当前最优解,希望全局最优。
  • 适用条件:贪心选择性质和最优子结构。
  • 示例:活动选择问题、霍夫曼编码、最小生成树(Kruskal和Prim算法)。

活动选择问题的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 活动结构体
struct Activity {
int start;
int finish;

Activity(int s, int f) : start(s), finish(f) {}
};

// 比较函数,按结束时间排序
bool compareActivities(const Activity& a, const Activity& b) {
return a.finish < b.finish;
}

// 贪心算法选择最多不重叠活动
vector<Activity> selectActivities(vector<Activity>& activities) {
vector<Activity> selected;

// 按结束时间排序
sort(activities.begin(), activities.end(), compareActivities);

// 选择第一个活动
selected.push_back(activities[0]);
int lastFinish = activities[0].finish;

// 依次选择后续活动
for (size_t i = 1; i < activities.size(); i++) {
if (activities[i].start >= lastFinish) {
selected.push_back(activities[i]);
lastFinish = activities[i].finish;
}
}

return selected;
}

8.2 动态规划

  • 策略:将问题分解为子问题,存储子问题解避免重复计算。
  • 适用条件:重叠子问题和最优子结构。
  • 示例:0-1背包问题、最长公共子序列(LCS)、最短路径(Floyd-Warshall算法)。

0-1背包问题的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 0-1背包问题(动态规划)
int knapsack(int W, const vector<int>& weights, const vector<int>& values, int n) {
// dp[i][w]:前i个物品,背包容量w时的最大价值
vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));

for (int i = 1; i <= n; i++) {
for (int w = 0; w <= W; w++) {
if (weights[i - 1] <= w) {
// 选择或不选择第i个物品
dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]], dp[i - 1][w]);
} else {
// 不选择第i个物品
dp[i][w] = dp[i - 1][w];
}
}
}

return dp[n][W];
}

8.3 分治算法

  • 策略:将问题分解为独立子问题,递归解决后合并结果。
  • 适用条件:问题可分解为规模更小的子问题,子问题解可合并为原问题解。
  • 示例:归并排序、快速排序、大整数乘法、Strassen矩阵乘法。

8.4 回溯算法

  • 策略:尝试所有可能的解,通过剪枝避免无效路径。
  • 适用条件:组合优化问题,解空间较大但可通过剪枝减少搜索空间。
  • 示例:八皇后问题、迷宫问题、子集和问题。

八皇后问题的C++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 八皇后问题
class NQueens {
private:
int n;
vector<vector<string>> solutions;

// 检查是否可以在board[row][col]放置皇后
bool isSafe(const vector<string>& board, int row, int col) {
// 检查列
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') {
return false;
}
}

// 检查左上到右下对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') {
return false;
}
}

// 检查右上到左下对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q') {
return false;
}
}

return true;
}

// 回溯函数
void backtrack(vector<string>& board, int row) {
if (row == n) {
solutions.push_back(board);
return;
}

for (int col = 0; col < n; col++) {
if (isSafe(board, row, col)) {
board[row][col] = 'Q';
backtrack(board, row + 1);
board[row][col] = '.'; // 回溯
}
}
}
public:
NQueens(int size) : n(size) {}

vector<vector<string>> solve() {
vector<string> board(n, string(n, '.'));
backtrack(board, 0);
return solutions;
}

void printSolutions() {
for (size_t i = 0; i < solutions.size(); i++) {
cout << "Solution " << i + 1 << ":" << endl;
for (const string& row : solutions[i]) {
cout << row << endl;
}
cout << endl;
}
}
};

9. 实际应用与性能优化

9.1 数据结构选择原则

  • 根据操作频率
    • 频繁查找:哈希表、平衡树(红黑树、AVL树)。
    • 频繁插入/删除:链表、二叉搜索树。
    • 频繁顺序访问:数组、链表。
  • 根据数据规模
    • 小数据:数组(空间效率高)。
    • 大数据:链表、动态数组(无容量限制)。
  • 根据内存限制
    • 空间紧张:紧凑结构(数组)。
    • 空间充足:操作高效结构(哈希表)。

9.2 性能优化技巧

  • 缓存优化
    • 时间局部性:最近访问的数据可能再次被访问,使用缓存。
    • 空间局部性:连续访问的数据应存储在连续内存(如数组)。
  • 代码优化
    • 内联函数:减少函数调用开销。
    • 循环展开:减少循环控制开销。
    • 避免不必要的计算:预处理、缓存中间结果。
  • 并行计算
    • 多线程:利用多核CPU。
    • GPU加速:适合并行度高的计算(如矩阵乘法)。

9.3 C++特性的利用

  • 模板:实现通用数据结构和算法,如 vectormapset 等。
  • STL:标准模板库提供了丰富的数据结构和算法,如排序、查找、容器等。
  • 智能指针:自动管理内存,避免内存泄漏,如 unique_ptrshared_ptr
  • 移动语义:减少不必要的拷贝,提高性能,如 std::move

10. 总结

《数据结构、算法与应用:C++语言描述》第2版是一本系统讲解数据结构与算法的经典教材,通过C++实现,帮助读者理解数据结构的设计原理和算法分析方法。本书的核心价值在于:

  1. C++特性的充分利用:通过模板、STL等C++特性,实现通用且高效的数据结构和算法。
  2. 理论与实践结合:不仅讲解概念,还提供完整的C++实现示例,便于理解与应用。
  3. 算法分析的深入:详细的时间复杂度和空间复杂度分析,帮助读者评估算法性能。
  4. 应用场景的丰富:结合实际问题,展示数据结构和算法的应用价值。

通过学习本书,读者可以掌握数据结构与算法的核心原理,提高代码效率,为解决复杂问题打下坚实基础。在实际编程中,应根据具体场景选择合适的数据结构和算法,平衡时间复杂度与空间复杂度,编写高效、可维护的代码。

数据结构与算法是计算机科学的基础,无论编程语言如何变化,其核心原理始终不变。掌握好数据结构与算法,不仅可以提高编程能力,还可以培养逻辑思维能力,为未来的职业发展奠定坚实基础。

参考资料

  • 《数据结构、算法与应用:C++语言描述》(第2版),Sartaj Sahni 著
  • C++标准库文档
  • 算法可视化工具:Visualgo(https://visualgo.net/)
  • 数据结构与算法在线课程:Coursera、edX