C++与Python对应常用各容器详解

文章目录


前言

本文为自学使用,欢迎大家补充


一、动态数组

  • 动态数组 是一种可以在运行时自动改变大小(扩容或缩容)的数组数据结构。它提供了在序列末尾高效添加/删除元素的能力,并且支持随机访问(通过索引直接访问任何元素)。

C++动态数组:std::vector

std::vector是 C++ 标准模板库(STL)中提供的容器,它就是一个典型的动态数组。它管理着一个动态分配的连续内存空间,当新元素插入导致空间不足时,它会自动分配一个更大的新数组,并将原有元素移动过去。

1. 头文件

#include <vector>

2. 初始化

// 空向量
std::vector<int> vec1;

// 指定初始大小和初始值(可选,默认为0)
std::vector<int> vec2(5); // 5个元素,每个都是0
std::vector<int> vec3(5, 10); // 5个元素,每个都是10

// 使用初始化列表 (C++11及以上)
std::vector<int> vec4 = {1, 2, 3, 4, 5};

// 从数组或其他容器初始化
int arr[] = {1, 2, 3};
std::vector<int> vec5(arr, arr + 3); // 使用指针范围

std::vector<int> vec6(vec4.begin(), vec4.end()); // 使用迭代器范围

3. 元素访问

std::vector<int> v = {10, 20, 30};

// 1. 使用下标运算符 [] (不检查边界,访问越界行为未定义)
int a = v[1]; // 20

// 2. 使用 at() 成员函数 (检查边界,越界抛出 std::out_of_range 异常)
int b = v.at(1); // 20

// 3. 访问第一个和最后一个元素
int first = v.front(); // 10
int last = v.back();  // 30

// 4. 直接访问底层数组(C++11及以上)
int* data_ptr = v.data();

4. 容量相关

v = {1, 2, 3};
// 获取当前元素个数
size_t size = v.size(); // 3

// 获取当前分配的容量(能容纳的元素总数, >= size)
size_t cap = v.capacity(); // 可能为 3, 4, 或其他 >=3 的值

// 检查容器是否为空
bool isEmpty = v.empty(); // false

// 改变向量的大小(size),新增元素用默认值填充
v.resize(5); // v 变为 {1, 2, 3, 0, 0}
v.resize(2); // v 变为 {1, 2} (多余的元素被销毁)

// 改变向量的容量(capacity),通常用于预分配内存以避免多次扩容
v.reserve(100); // 提前分配至少100个元素的空间,size()不变

5. 修改内容(增删改)

// 1. 在末尾添加元素 (均摊时间复杂度 O(1))
v.push_back(40); // v: {1, 2, 3, 40}

// 2. 在末尾删除元素 (时间复杂度 O(1))
v.pop_back(); // v: {1, 2, 3}

// 3. 在指定位置(迭代器指定)之前插入元素 (时间复杂度 O(n))
auto it = v.begin() + 1; // 指向第二个元素 ‘2'
v.insert(it, 99); // v: {1, 99, 2, 3}

// 4. 删除指定位置(迭代器指定)或范围的元素 (时间复杂度 O(n))
it = v.begin() + 2; // 指向第三个元素 ‘2'
v.erase(it); // v: {1, 99, 3}

// 5. 清空所有元素
v.clear(); // v: {}

// 6. 交换两个向量的内容
std::vector<int> other = {7, 8, 9};
v.swap(other); // v 变为 {7, 8, 9}, other 变为 {}

6. 迭代器

用于遍历或指定位置。

std::vector<int> v = {1, 2, 3};

// 正向迭代器
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}
// 使用 auto (C++11)
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}
// 范围 for 循环 (C++11)
for (int num : v) {
    std::cout << num << " ";
}

// 反向迭代器
for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
    std::cout << *rit << " "; // 输出: 3 2 1
}

Python动态数组:list

Python 的内置类型 list就是一个功能强大的动态数组。它的行为与 C++ 的 vector非常相似,但语法更加简洁和高级。

1. 初始化

# 空列表
list1 = []

# 使用初始化列表
list2 = [1, 2, 3, 4, 5]

# 使用 list() 构造函数
list3 = list() # 空列表
list4 = list(range(5)) # [0, 1, 2, 3, 4]
list5 = list(“hello”) # [‘h‘, ’e‘, ’l‘, ’l‘, ’o’] (从可迭代对象转换)

2. 元素访问

v = [10, 20, 30]

# 1. 使用下标索引 (支持负数索引,越界抛出 IndexError)
a = v[1]  # 20
last = v[-1] # 30 (倒数第一个)

# 2. 切片 (Slice) - 获取子列表 [start:stop:step]
sub = v[1:3] # [20, 30] ( stop 索引不包含)
sub2 = v[::2] # [10, 30] (每隔一个取一个)

3. 容量相关

v = [1, 2, 3]
# 获取当前元素个数
length = len(v) # 3

# 检查列表是否为空
is_empty = not v # 或者 len(v) == 0

4. 修改内容(增删改)

v = [1, 2, 3]

# 1. 在末尾添加元素 (时间复杂度 O(1))
v.append(4) # v: [1, 2, 3, 4]

# 2. 扩展列表,将另一个可迭代对象的元素逐个添加到末尾 (时间复杂度 O(k))
v.extend([5, 6]) # v: [1, 2, 3, 4, 5, 6]

# 3. 在指定位置插入元素 (时间复杂度 O(n))
v.insert(1, 99) # 在索引1的位置插入99, v: [1, 99, 2, 3, 4, 5, 6]

# 4. 按值删除元素(删除第一个匹配项) (时间复杂度 O(n))
v.remove(99) # v: [1, 2, 3, 4, 5, 6]

# 5. 按索引删除并返回元素 (时间复杂度 O(n), pop() 是 O(1))
popped_val = v.pop(2) # 删除索引2的元素 ‘3‘, v: [1, 2, 4, 5, 6]
popped_last = v.pop() # 删除并返回最后一个元素 ‘6‘, v: [1, 2, 4, 5]

# 6. 清空所有元素
v.clear() # v: []

# 7. 按索引赋值修改
v = [1, 2, 3]
v[1] = 20 # v: [1, 20, 3]

# 8. 切片赋值 (可以替换、插入、删除)
v = [1, 2, 3, 4, 5]
v[1:3] = [20, 30, 40] # 替换,v: [1, 20, 30, 40, 4, 5]
v[1:1] = [99, 88] # 插入,v: [1, 99, 88, 20, 30, 40, 4, 5]
v[2:4] = [] # 删除,v: [1, 99, 30, 40, 4, 5]

5. 其他常用操作

v = [1, 2, 3, 2, 4]

# 查找元素第一次出现的索引 (找不到抛出 ValueError)
index = v.index(2) # 1

# 计算某个元素出现的次数
count = v.count(2) # 2

# 反转列表本身 (in-place)
v.reverse() # v: [4, 2, 3, 2, 1]

# 对列表本身进行排序 (in-place)
v.sort() # v: [1, 2, 2, 3, 4]
v.sort(reverse=True) # 降序排序

# 使用 sorted() 函数获取排序后的新列表,原列表不变
new_list = sorted(v, reverse=True)

# 列表推导式 (创建新列表的强大工具)
squares = [x**2 for x in v] # [1, 4, 4, 9, 16]
even_squares = [x**2 for x in v if x % 2 == 0] # [4, 4, 16]

6. 遍历

v = [‘a‘, ’b‘, ’c’]

# 直接遍历元素
for item in v:
    print(item)

# 需要索引时使用 enumerate
for index, item in enumerate(v):
    print(f“Index {index}: {item})

二、双端队列

双端队列是一种具有队列和栈性质的抽象数据类型,允许在队列的前端和后端都进行插入和删除操作。

特性C++ (std::deque)Python (collections.deque)
实现方式动态分配的分块数组(通常)双向链表
随机访问支持,通过 [ ]运算符,常数时间 O(1)支持,通过 [ ]运算符,但 O(n) 时间(需遍历)
中间插入/删除效率较低,线性时间 O(n)效率较低,线性时间 O(n)
两端操作高效,分摊常数时间 O(1)高效,常数时间 O(1)
线程安全否(需要手动加锁)(其原子操作是线程安全的)
功能扩展是 STL 的一部分,算法丰富专为两端操作设计,提供旋转等实用方法

C++双端队列:std::deque

std::deque(Double Ended QUEue)是 C++ 标准模板库中的一种容器适配器,包含在 <deque>头文件中。

1. 包含头文件和初始化

#include <deque>
#include <iostream>

using namespace std;

// 初始化
deque<int> dq; // 创建一个空的双端队列
deque<int> dq1 = {1, 2, 3, 4}; // 初始化列表
deque<int> dq2(5, 100); // 创建包含5个100的deque
deque<int> dq3(dq1.begin(), dq1.end()); // 通过迭代器范围初始化

2. 元素访问

dq = {10, 20, 30, 40};

// 通过索引访问(不进行边界检查)
cout << dq[1]; // 输出 20

// 使用 at() 访问(进行边界检查,越界则抛出 std::out_of_range 异常)
cout << dq.at(2); // 输出 30

// 访问首尾元素
cout << dq.front(); // 输出 10(第一个元素)
cout << dq.back();  // 输出 40(最后一个元素)

3. 修改操作(两端)

// 在尾部添加元素
dq.push_back(50);  // dq: {10, 20, 30, 40, 50}
dq.emplace_back(60); // 更高效,直接在尾部构造元素

// 在头部添加元素
dq.push_front(0); // dq: {0, 10, 20, 30, 40, 50, 60}
dq.emplace_front(-10); // 直接在头部构造元素

// 删除尾部元素
dq.pop_back(); // 删除60,无返回值

// 删除头部元素
dq.pop_front(); // 删除-10,无返回值

4. 修改操作(中间)

// 插入元素(效率较低)
auto it = dq.begin() + 2; // 获取指向第三个元素的迭代器
dq.insert(it, 25); // 在第三个位置插入25
// dq: {0, 10, 25, 20, 30, 40, 50}

// 删除元素(效率较低)
it = dq.begin() + 3;
dq.erase(it); // 删除第四个元素(20)
// dq: {0, 10, 25, 30, 40, 50}

// 清空队列
dq.clear();

5. 容量操作

cout << dq.size(); // 返回元素数量
cout << dq.empty(); // 判断是否为空
dq.resize(10); // 将大小调整为10,新元素默认初始化
dq.shrink_to_fit(); // 请求减少容量以适应大小(非强制)

6. 迭代器

// 正向迭代
for (auto it = dq.begin(); it != dq.end(); ++it) {
    cout << *it << " ";
}
// 反向迭代
for (auto rit = dq.rbegin(); rit != dq.rend(); ++rit) {
    cout << *rit << " ";
}
// 范围for循环
for (auto &elem : dq) {
    cout << elem << " ";
}

Python 中的双端队列:collections.deque

deque是 Python 标准库 collections模块中的一个类,专为高效的两端操作而设计。

1. 导入和初始化

from collections import deque

# 初始化
dq = deque() # 创建一个空的双端队列
dq1 = deque([1, 2, 3, 4]) # 从可迭代对象初始化
dq2 = deque('hello') # dq2: deque(['h', 'e', 'l', 'l', 'o'])
dq3 = deque(maxlen=5) # 创建一个最大长度为5的deque(满时自动丢弃另一端元素)

2. 元素访问

dq = deque([10, 20, 30, 40])

# 通过索引访问(相对较慢,因为需要遍历)
print(dq[1]) # 输出 20

# 访问首尾元素
print(dq[0]) # 输出 10(等同于 dq[0])
print(dq[-1]) # 输出 40(等同于 dq[-1])

3. 修改操作(两端)

# 在尾部添加元素
dq.append(50) # dq: deque([10, 20, 30, 40, 50])
dq.appendleft(0) # 在头部添加元素,dq: deque([0, 10, 20, 30, 40, 50])

# 扩展 deque(添加多个元素)
dq.extend([60, 70]) # 在尾部扩展,dq: deque([0, 10, 20, 30, 40, 50, 60, 70])
dq.extendleft([-10, -20]) # 在头部扩展(注意:会将可迭代对象逆序插入)
# 首先插入 -20,然后插入 -10
# dq: deque([-20, -10, 0, 10, 20, 30, 40, 50, 60, 70])

# 删除元素
popped_right = dq.pop() # 删除并返回尾部元素 (70)
popped_left = dq.popleft() # 删除并返回头部元素 (-20)

4. 其他实用操作

# 旋转(非常实用)
dq = deque([1, 2, 3, 4, 5])
dq.rotate(2)  # 向右旋转2步(正数)
# dq: deque([4, 5, 1, 2, 3])
dq.rotate(-1) # 向左旋转1步(负数)
# dq: deque([5, 1, 2, 3, 4])

# 计数
count = dq.count(2) # 计算某个值出现的次数

# 查找和删除(注意:删除第一个匹配的值,若找不到则引发 ValueError)
dq.remove(3) # 删除值为3的第一个元素

# 清空队列
dq.clear()

# 复制队列
dq_copy = dq.copy()

5. 容量操作

print(len(dq)) # 返回元素数量
if not dq: # 判断是否为空
    print("Deque is empty")

# 最大长度属性
limited_dq = deque(maxlen=3)
limited_dq.extend([1, 2, 3])
limited_dq.append(4) # 头部元素1被自动丢弃
# limited_dq: deque([2, 3, 4], maxlen=3)
print(limited_dq.maxlen) # 输出 3

三、双向链表

双向链表(Doubly Linked List)是一种常见的线性数据结构,每个节点(Node)除了存储数据外,还包含两个指针,分别指向前一个节点(prev)和后一个节点(next)。这使得遍历可以双向进行,但同时也增加了内存开销和操作的复杂性。


C++双向链表

在 C++ 中,标准模板库(STL)提供了 std::list容器,它是一个典型的双向链表的实现。

1. 头文件

#include <list>

2. 声明与初始化

// 空链表
std::list<int> myList;

// 初始化有 n 个元素,默认值为 0
std::list<int> myList(5);

// 初始化有 n 个元素,每个值都为 value
std::list<int> myList(5, 10); // {10, 10, 10, 10, 10}

// 用初始化列表
std::list<int> myList = {1, 2, 3, 4, 5};

// 用迭代器范围初始化(例如从数组或其他容器)
int arr[] = {1, 2, 3};
std::list<int> myList(std::begin(arr), std::end(arr));

3. 元素访问

注意:std::list不支持随机访问(如 operator[].at()),只能通过迭代器进行顺序访问。

std::list<int> l = {10, 20, 30};

// 访问第一个和最后一个元素
std::cout << l.front() << std::endl; // 输出: 10
std::cout << l.back() << std::endl;  // 输出: 30

// 使用迭代器进行遍历(正向)
for (std::list<int>::iterator it = l.begin(); it != l.end(); ++it) {
    std::cout << *it << " ";
}
// C++11 起,使用 auto 更简洁
for (auto it = l.begin(); it != l.end(); ++it) {
    std::cout << *it << " ";
}
// 或使用范围 for 循环
for (const auto &elem : l) {
    std::cout << elem << " ";
}

4. 容量操作

std::list<int> l = {1, 2, 3};
std::cout << l.empty() << std::endl; // 检查是否为空,输出: 0 (false)
std::cout << l.size() << std::endl;  // 获取元素个数,输出: 3
// l.max_size(); // 返回链表可容纳的最大元素数量(一个很大的数)

5. 修改器(增删改)

std::list<int> l;

// --- 添加元素 ---
l.push_back(40);     // 在末尾添加: {40}
l.push_front(10);    // 在开头添加: {10, 40}
l.insert(l.begin(), 5); // 在指定迭代器位置前插入: {5, 10, 40}
auto it = l.begin();
std::advance(it, 2); // 将迭代器 it 向后移动 2 位,指向 40
l.insert(it, 20);    // 在 40 前插入 20: {5, 10, 20, 40}

// 可以插入多个相同值或一个区间
l.insert(l.begin(), 2, 0); // 在开头插入两个 0: {0, 0, 5, 10, 20, 40}

// --- 删除元素 ---
l.pop_back();         // 删除末尾元素: {0, 0, 5, 10, 20}
l.pop_front();        // 删除开头元素: {0, 5, 10, 20}
it = l.begin();
std::advance(it, 2);  // it 指向 10
l.erase(it);          // 删除指定位置的元素 (10): {0, 5, 20}
// l.erase(l.begin(), l.end()); // 删除区间内的所有元素,清空链表
l.remove(5);          // 删除所有值为 5 的节点: {0, 20}
l.clear();            // 清空整个链表

// --- 其他操作 ---
std::list<int> l1 = {1, 3, 5};
std::list<int> l2 = {2, 4, 6};

l1.swap(l2);      // 交换两个链表的内容

l1.merge(l2);     // 合并两个已排序的链表(l2 会被清空)
l1.merge(l2, comp);// 使用比较函数 comp 合并

l1.sort();        // 对链表进行排序(因为不支持随机访问,STL 为 list 提供了专门的 sort 成员函数)
l1.sort(comp);          // 使用比较函数 comp 排序

l1.reverse();     // 反转链表

l1.unique();      // 删除连续重复的元素(通常先排序再使用)
l1.unique(binary_pred); // 使用二元谓词判断重复

//拼接
lst.splice(pos_iter, other_list);              // 将 other_list 全部元素移到 pos_iter 前
lst.splice(pos_iter, other_list, other_iter);  // 移动 other_list 的单个元素
lst.splice(pos_iter, other_list, first_iter, last_iter); // 移动元素范围

//删除特定值
lst.remove(value);       // 删除所有等于 value 的元素
lst.remove_if(predicate); // 删除满足谓词的元素

6. 迭代器

除了 begin(), end(),还有反向迭代器和常量迭代器。

std::list<int> l = {1, 2, 3};
// 反向遍历
for (std::list<int>::reverse_iterator rit = l.rbegin(); rit != l.rend(); ++rit) {
    std::cout << *rit << " "; // 输出: 3 2 1
}
// 常量迭代器(用于避免修改元素)
for (std::list<int>::const_iterator cit = l.cbegin(); cit != l.cend(); ++cit) {
    // *cit = 5; // 错误!不能修改
}

Python双向链表

Python 的标准库没有直接提供名为“双向链表”的数据结构。但是,有两种常见的替代方案:

使用 collections.deque(推荐):这是一个双向队列,底层通常使用双向链表实现,提供了高效的头尾操作。

好的,我们来详细探讨一下 C++ 和 Python 中的“静态数组”概念及其用法。

首先,需要明确一个关键点:“静态数组”的定义在两种语言中有根本性的不同

  • 在 C++ 中,“静态”通常指两件事:
    1. 内存分配方式:在编译期确定大小,在程序的栈(Stack)内存上分配。其生命周期与所在的作用域相同(例如,函数结束时自动销毁)。
    2. 关键字 static:用于修饰变量,使其生命周期变为整个程序运行期(在静态存储区分配),但作用域不变。
  • 在 Python 中:语言本身并没有内置的、严格意义上的“静态数组”。我们通常用以下两种方式来模拟或实现类似功能:
    1. array模块:提供了一个类似于数组的高效数据结构,用于存储同类型的基本数据类型(如整型、浮点型)。
    2. 第三方库 NumPy:提供了强大、高性能的多维数组对象 ndarray,这是科学计算领域的事实标准。

四、静态数组

C++静态数组

C++ 中的静态数组是语言内置的核心特性,其大小在编译时必须已知且固定。

1. 定义和初始化

// 1. 基本定义:类型 名字[大小];
int arr[5]; // 定义一个包含5个整数的数组,其值未初始化(通常是垃圾值)

// 2. 初始化列表:类型 名字[大小] = {值1, 值2, ...};
int arr1[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr2[5] = {1, 2, 3};       // 部分初始化,剩余元素被初始化为0
int arr3[] = {1, 2, 3, 4, 5};  // 编译器自动推断数组大小为5

// 3. 使用 static 关键字(改变生命周期)
void myFunction() {
    static int staticArr[3] = {10, 20, 30}; // 在静态存储区分配,函数调用结束后依然存在
}

2. 访问元素

int arr[5] = {10, 20, 30, 40, 50};

// 1. 通过下标运算符 [] (从0开始)
int first = arr[0]; // 10
arr[2] = 100;       // 将第三个元素改为100

// 2. 通过指针算术(数组名是首元素的地址)
int second = *(arr + 1); // 20,等价于 arr[1]

// 注意:C++ 不进行数组边界检查,访问 arr[5] 会导致未定义行为(程序可能崩溃)

3. 遍历数组

int arr[] = {1, 2, 3, 4, 5};
size_t length = sizeof(arr) / sizeof(arr[0]); // 计算数组长度

// 1. for 循环(使用下标)
for (int i = 0; i < length; ++i) {
    std::cout << arr[i] << " ";
}

// 2. 范围for循环 (C++11)
for (int element : arr) {
    std::cout << element << " ";
}

// 3. 使用指针
for (int* ptr = arr; ptr < arr + length; ++ptr) {
    std::cout << *ptr << " ";
}

4. 作为函数参数

数组会退化为指针,因此必须同时传递大小信息。

// 函数声明(三种等价形式)
void printArray(int* arr, size_t size);
void printArray(int arr[], size_t size);   // 更清晰的写法
void printArray(int arr[5], size_t size); // 这里的[5]会被编译器忽略,没有意义

// 函数定义
void printArray(int arr[], size_t size) {
    for (size_t i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
}

// 调用
int myArr[] = {1, 2, 3, 4, 5};
printArray(myArr, sizeof(myArr) / sizeof(myArr[0]));

5. 多维静态数组

// 定义一个 2x3 的二维数组
int matrix[2][3] = { {1, 2, 3},
                     {4, 5, 6} };

// 访问元素
int value = matrix[0][1]; // 2

// 遍历
for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
        std::cout << matrix[i][j] << " ";
    }
    std::cout << std::endl;
}

6. 优缺点

  • 优点:极致的性能。在栈上分配,分配和释放速度快,无内存碎片。
  • 缺点
    • 大小固定,无法动态改变。
    • 作为函数参数传递时,会丢失大小信息(退化为指针)。
    • 越界访问不安全,会导致未定义行为。

Python“静态数组”

Python 没有内置的静态数组,我们使用 array模块或 numpy库来实现类似功能。

1. 使用 array模块

array模块提供了一种高效的数组结构,存储相同类型的基本数值类型(如 int, float)。

用法:

import array

# 1. 创建数组:array.typecode(iterable)
# 常用类型码:'i'(有符号int),'f'(float),'d'(double)
arr = array.array('i', [1, 2, 3, 4, 5]) # 创建一个整型数组
# arr = array.array('i’) # 创建一个空数组

# 2. 访问元素(与列表类似,支持索引和切片)
print(arr[0])    # 输出: 1
print(arr[1:3])  # 输出: array('i', [2, 3])

# 3. 修改元素
arr[0] = 10

# 4. 添加元素(动态扩容,因此不是“静态”的)
arr.append(6)       # 在末尾添加一个元素
arr.extend([7, 8]) # 在末尾扩展多个元素

# 5. 遍历数组
for num in arr:
    print(num)

# 6. 常用方法
arr.insert(1, 99) # 在索引1处插入99
arr.pop()         # 移除并返回最后一个元素
arr.remove(99)    # 移除第一个出现的99
arr.tolist()      # 将数组转换为普通列表

特点:

  • list更节省内存,因为元素类型固定。
  • 支持动态扩容,行为上更像 C++ 的 std::vector,而不是静态数组。
  • 只能存储基本数值类型,不能存储任意对象(如字符串、其他列表)。

2. 使用 NumPy

NumPyndarray是 Python 科学计算的基石,提供了真正的固定类型、多维的高性能数组。

用法:

import numpy as np

# 1. 创建数组
arr = np.array([1, 2, 3, 4, 5])   # 一维数组
matrix = np.array([[1, 2, 3], [4, 5, 6]]) # 二维数组

# 2. 创建具有特定大小和初始值的“静态”数组(更接近C++概念)
zeros = np.zeros(5)        # 创建长度为5,全部为0的数组
ones = np.ones((2, 3))     # 创建2x3,全部为1的数组
full = np.full(4, 7)       # 创建长度为4,全部为7的数组
empty = np.empty(3)        # 创建长度为3的未初始化数组(速度快)

# 3. 访问和修改(功能极其强大)
print(arr[0])           # 索引
print(matrix[0, 1])     # 多维索引
print(arr[1:4])         # 切片

arr[0] = 10             # 修改
matrix[:, 1] = [0, 0]   # 修改第二列的所有行

# 4. 数组属性
print(arr.shape)    # 数组的形状 (5,)
print(arr.dtype)    # 数组的数据类型 int32(或int64)
print(arr.size)     # 数组元素总数 5

# 5. 数组操作(矢量化运算,无需循环)
arr_squared = arr ** 2          # 对每个元素平方
arr_sum = arr + np.array([10, 20, 30, 40, 50]) # 数组相加

# 6. 重塑数组大小(返回新视图,不改变数据总数)
new_arr = arr.reshape(1, 5) # 将一维数组重塑为1x5的二维数组

特点:

  • 性能极高:底层由 C 实现,操作是矢量化的,避免了 Python 循环的开销。
  • 功能丰富:支持复杂的切片、索引、广播、线性代数运算等。
  • 大小固定?ndarray在创建后大小是固定的,reshape操作返回的是新视图而非修改原大小。要改变大小需使用 np.resizenp.append(这些操作会创建新数组)。

五、元组

核心概念总结

特性Python 元组C++ 元组 (std::tuple)
本质内置的、不可变的序列类型标准库模板,编译期确定的静态类型集合
可变性不可变元组本身不可变,但元素若为引用或指针则可间接修改
元素类型可以不同,动态类型(但类型是运行时信息)可以不同,静态类型(类型在编译时确定)
访问方式索引 t[i], 切片 t[start:stop], 迭代, 解包std::get<I>(t)std::get<T>(t)(编译时索引)
内存与性能动态分配,运行时开销编译期确定布局,通常无运行时开销(零成本抽象)
主要用途轻量级数据结构、多返回值、函数参数、字典键多返回值、异构数据聚合、模拟多返回值、与库交互

C++ 元组 (std::tuple)

C++ 的 std::tuple是标准库 (<tuple>) 提供的一个类模板,用于将固定数量的、类型可能不同的元素打包成一个单一对象。它是一个编译期概念。

1. 包含头文件和创建元组

#include <tuple>
#include <string>
#include <iostream>

// 使用 std::make_tuple (自动推导类型)
auto myTuple = std::make_tuple(10, "Hello", 3.14); // tuple<int, const char*, double>

// 使用 std::tuple 构造函数 (显式指定类型)
std::tuple<int, std::string, double> myTuple2(10, "Hello", 3.14);

// 创建空元组
std::tuple<> emptyTuple;

// C++17 起支持的类模板参数推导
std::tuple deducedTuple(10, std::string("Hello"), 3.14); // tuple<int, std::string, double>

2. 访问元素

使用 std::get,参数是编译期常量的索引或类型。

auto myTuple = std::make_tuple(10, "Hello", 3.14);

// 通过索引访问(索引从0开始)
int intVal = std::get<0>(myTuple);
const char* strVal = std::get<1>(myTuple);
double doubleVal = std::get<2>(myTuple);

// 通过类型访问(类型必须唯一,否则编译错误)
int intVal2 = std::get<int>(myTuple);
// auto strVal2 = std::get<const char*>(myTuple); 
// 注意:如果多个元素类型相同,不能用此法访问第二个

3. 元组解包(Structured Bindings - C++17)

这是 C++ 中类似 Python 解包的强大特性。

#include <iostream>
#include <tuple>

std::tuple<int, std::string, double> getData() {
    return std::make_tuple(42, "Alice", 87.5);
}

int main() {
    // 传统方式,使用 std::tie (C++11)
    int id;
    std::string name;
    double score;
    std::tie(id, name, score) = getData(); // std::tie 创建了一个引用元组

    // 现代方式,使用结构化绑定 (C++17)
    auto [id2, name2, score2] = getData(); // 直接声明并初始化变量

    std::cout << id2 << ", " << name2 << ", " << score2 << std::endl;
    return 0;
}

4. 其他常用操作

auto t1 = std::make_tuple(1, 2, 3);
auto t2 = std::make_tuple(1, 2, 3);

// 比较操作 (==, !=, <, <=, >, >=)
// 按字典序比较元素
if (t1 == t2) {
    std::cout << "Tuples are equal" << std::endl;
}

// 获取元组大小(元素个数)
size_t size = std::tuple_size<decltype(t1)>::value; // C++11/14
// 或者 C++17 起
size_t size17 = std::tuple_size_v<decltype(t1)>; // 3

// 获取元素类型
// std::tuple_element<0, decltype(t1)>::type 是 int

// 连接元组 (C++11 之后需要自己实现或使用 boost.hana)
// auto t3 = std::tuple_cat(t1, t2); // 结果是 (1,2,3,1,2,3)

5. 常用场景

  • 函数返回多个值:这是最经典的用法。

    std::tuple<bool, std::string, int> parseInput(const std::string& input) {
        // ... 解析逻辑
        if (success) {
            return std::make_tuple(true, resultString, resultCode);
        } else {
            return std::make_tuple(false, "Error", -1);
        }
    }
    
  • 需要将多个数据作为单一实体传递或存储时,但又不想为此专门定义一个 struct

  • 与标准库其他组件交互:例如 std::map::insert返回一个 tuple<iterator, bool>

Python 元组

Python 的元组是一个不可变有序的序列,可以包含任意类型的对象。

1. 创建元组

# 使用圆括号(推荐)
t1 = ()          # 空元组
t2 = (1, )       # 单元素元组(逗号必不可少)
t3 = (1, "hello", 3.14) # 多元素元组
t4 = 1, "hello", 3.14   # 括号可以省略,这同样创建一个元组

# 使用 tuple() 构造函数
t5 = tuple()           # 空元组
t6 = tuple([1, 2, 3])  # 从可迭代对象(如列表)转换

2. 访问元素

t = ('a', 'b', 'c', 'd')

# 索引访问(从0开始)
first = t[0]   # 'a'
last = t[-1]   # 'd' (负索引表示从后向前)

# 切片(返回一个新元组)
sub_t = t[1:3] # ('b', 'c')

# 迭代
for item in t:
    print(item)

3. 元组解包(Unpacking)

这是 Python 元组非常强大和常用的特性。

t = (1, 2, 3)
a, b, c = t # a=1, b=2, c=3

# 在函数参数中使用*
def sum(a, b):
    return a + b
args = (5, 10)
result = sum(*args) # 等价于 sum(5, 10)

# 用于交换变量
a, b = b, a # 无需临时变量

# 使用 * 收集多余元素
a, b, *rest = (1, 2, 3, 4, 5) # a=1, b=2, rest=[3,4,5]
a, *middle, b = (1, 2, 3, 4, 5) # a=1, middle=[2,3,4], b=5

4. 方法

由于不可变,元组的方法很少。

t = (1, 2, 2, 3)

# count(x) - 返回元素 x 在元组中出现的次数
count = t.count(2) # 2

# index(x) - 返回元素 x 在元组中第一次出现的索引
index = t.index(3) # 3

5. 常用场景

  • 函数返回多个值return success, result, message

  • 作为字典的键:因为不可变且可哈希。

    location_dict = {}
    point = (10, 20) # 一个坐标元组
    location_dict[point] = "Home"
    
  • 格式化字符串print("Name: %s, Age: %d" % ("Alice", 25))

  • 保护数据:防止意外修改序列内容。


六、无序关联容器

  • 无序关联容器 基于哈希表实现,通过计算元素的哈希值来快速定位存储位置,从而实现平均情况下 O(1) 时间复杂度的查找、插入和删除操作。
  • 它们的元素没有特定的顺序(与基于红黑树的有序容器如 std::mapstd::set相对)。
  • 性能可能在某些情况下(如哈希冲突严重时)退化为 O(n)

C++无序关联容器

C++ 在 STL(标准模板库)中提供了 4 种主要的无序关联容器,定义在 <unordered_set><unordered_map>头文件中。

1. std::unordered_set

  • 含义: 一个唯一键的集合,每个元素的值就是键本身。
  • 特性
    • 键是唯一的。
    • 元素不可修改(修改会破坏哈希结构),但可以插入和删除。
    • 查找效率极高。

主要用法:

#include <iostream>
#include <unordered_set>

int main() {
    // 1. 初始化
    std::unordered_set<int> mySet = {5, 2, 8, 2, 9}; // {5, 2, 8, 9} (重复的2被忽略)

    // 2. 插入元素
    mySet.insert(3);
    mySet.emplace(1); // 原地构造,效率更高

    // 3. 查找元素
    auto it = mySet.find(2);
    if (it != mySet.end()) {
        std::cout << "Found: " << *it << std::endl;
    }

    // 4. 删除元素
    mySet.erase(8);      // 通过值删除
    mySet.erase(it);     // 通过迭代器删除

    // 5. 遍历元素 (顺序是无序且不可预测的)
    for (const int& num : mySet) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 6. 其他常用操作
    std::cout << "Size: " << mySet.size() << std::endl;
    std::cout << "Bucket count: " << mySet.bucket_count() << std::endl;
    mySet.clear(); // 清空

    return 0;
}

2. std::unordered_multiset

  • 含义: 一个允许重复键的集合。
  • 特性: 除了允许重复键,其他特性与 unordered_set相同。

主要用法:

#include <iostream>
#include <unordered_set>

int main() {
    std::unordered_multiset<int> myMultiSet = {5, 2, 8, 2, 9}; // {5, 2, 8, 2, 9}

    // 插入重复元素
    myMultiSet.insert(2);

    // 查找元素 2,会返回指向第一个匹配元素的迭代器
    auto range = myMultiSet.equal_range(2); // 获取所有键为2的元素范围
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << *it << " ";
    }
    // 输出: 2 2 2 (顺序依赖于实现,但所有2都会在一起)

    return 0;
}

3. std::unordered_map

  • 含义: 由键值对组成的集合,键是唯一的。
  • 特性
    • 键是唯一的。
    • 可以通过键快速查找、访问或插入对应的值。

主要用法:

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    // 1. 初始化
    std::unordered_map<std::string, int> myMap = {{"Alice", 25}, {"Bob", 30}};

    // 2. 插入元素
    myMap.insert({"Charlie", 28});
    myMap["David"] = 22; // 最常用的插入或修改方式

    // 3. 访问元素 (注意:使用 [] 访问不存在的键会创建它)
    std::cout << "Bob‘s age: " << myMap["Bob"] << std::endl;

    // 4. 修改元素
    myMap["Alice"] = 26;

    // 5. 查找元素 (推荐使用 find 而不是直接 [],避免意外创建)
    auto it = myMap.find("Eve");
    if (it == myMap.end()) {
        std::cout << "Eve not found." << std::endl;
    }

    // 6. 遍历元素 (键值对是 std::pair<const Key, T>)
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    // C++17 结构化绑定
    for (const auto& [key, value] : myMap) {
        std::cout << key << ": " << value << std::endl;
    }

    // 7. 删除元素
    myMap.erase("David");

    return 0;
}

4. std::unordered_multimap

  • 含义: 一个允许重复键的映射。
  • 特性: 除了允许重复键,其他特性与 unordered_map相同。没有 operator[],因为一个键可能对应多个值。

主要用法:

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    std::unordered_multimap<std::string, std::string> myMultiMap;

    // 插入元素 (允许重复键)
    myMultiMap.insert({"fruit", "apple"});
    myMultiMap.insert({"fruit", "banana"});
    myMultiMap.insert({"animal", "dog"});

    // 查找元素
    auto range = myMultiMap.equal_range("fruit");
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
    // 输出:
    // fruit: apple
    // fruit: banana
    
    return 0;
}

5. 高级用法:自定义哈希和比较函数

当键是自定义类型时,需要提供哈希函数和相等比较函数。

#include <iostream>
#include <unordered_set>

struct Person {
    std::string name;
    int age;

    // 重载 == 运算符用于比较
    bool operator==(const Person& other) const {
        return name == other.name && age == other.age;
    }
};

// 自定义哈希函数 (可以定义为函数对象)
struct PersonHash {
    std::size_t operator()(const Person& p) const {
        // 结合 name 和 age 的哈希值
        return std::hash<std::string>()(p.name) ^ (std::hash<int>()(p.age) << 1);
    }
};

int main() {
    // 使用自定义哈希和比较类型
    std::unordered_set<Person, PersonHash> personSet;

    personSet.insert({"Alice", 25});
    personMap.insert({"Bob", 30});

    // 如果 Person 没有重载 ==,则需要提供自定义比较类
    // 作为模板的第三个参数,例如:
    // std::unordered_set<Person, PersonHash, PersonEqual> personSet;

    return 0;
}

Python无序关联容器

Python 内置了两种主要的无序关联容器:dictset。在 Python 3.6+ 中,dict的实现保证了插入顺序,但从语言定义上,它们仍然被称为“无序集合”,因为你不应依赖其顺序进行逻辑计算(尽管在实践中现在可以)。set仍然是无序的。

1. set

  • 含义: 一个唯一且不可变对象的无序集合。
  • 特性
    • 元素必须是可哈希的(通常是不可变类型,如数字、字符串、元组)。
    • 元素是唯一的。
    • 可变,可以增删元素。

主要用法:

# 1. 初始化
my_set = {5, 2, 8, 2, 9}  # {8, 9, 2, 5} (重复的2被忽略,顺序随机)
my_set = set([5, 2, 8, 2, 9]) # 从列表创建

# 2. 添加元素
my_set.add(3)
my_set.update([1, 4, 6]) # 添加多个元素

# 3. 查找元素
# 集合没有“获取”操作,通常用 `in` 检查成员关系
if 2 in my_set:
    print(2 is in the set)

# 4. 删除元素
my_set.remove(8)   # 如果元素不存在,会引发 KeyError
my_set.discard(7) # 如果元素不存在,不会报错
popped_element = my_set.pop() # 随机移除并返回一个元素(因为无序)

# 5. 遍历元素 (顺序是无序且不可预测的)
for element in my_set:
    print(element)

# 6. 其他常用操作
print(len(my_set)) # 大小
my_set.clear()     # 清空

# 集合运算
other_set = {1, 2, 3, 10}
print(my_set | other_set)  # 并集 union()
print(my_set & other_set)  # 交集 intersection()
print(my_set - other_set)  # 差集 difference()

2. dict

  • 含义: 键值对映射。
  • 特性
    • 键必须是唯一的、可哈希的。
    • Python 3.7+ 官方保证,3.6+ 实现细节:字典会维护元素的插入顺序。
    • 值可以是任意类型。

主要用法:

# 1. 初始化
my_dict = {"Alice": 25, "Bob": 30}
my_dict = dict(Alice=25, Bob=30)

# 2. 插入/更新元素
my_dict["Charlie"] = 28
my_dict.update({"David": 22, "Alice": 26}) # 更新Alice,插入David

# 3. 访问元素
print(my_dict["Bob"]) # 30
# 使用 get() 避免 KeyError
print(my_dict.get("Eve"))      # 返回 None
print(my_dict.get("Eve", "N/A")) # 返回默认值 “N/A”

# 4. 删除元素
del my_dict["David"]
popped_value = my_dict.pop("Bob") # 移除并返回值
my_dict.popitem() # 移除并返回最后插入的键值对 (LIFO,因为有序)

# 5. 遍历元素
for key in my_dict:           # 遍历键
    print(key, my_dict[key])

for key, value in my_dict.items(): # 遍历键值对 (推荐)
    print(key, value)

for value in my_dict.values(): # 遍历值
    print(value)

# 6. 其他常用操作
print("Alice" in my_dict) # 检查键是否存在
print(len(my_dict))       # 获取元素数量

# 字典推导式
squares = {x: x*x for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

3. collections.defaultdict

  • 含义dict的一个子类,它提供了默认值功能,避免键不存在时的 KeyError
from collections import defaultdict

# 初始化时提供一个默认工厂函数
word_count = defaultdict(int) # int() 的默认值是 0
for word in ['a', 'b', 'a', 'c']:
    word_count[word] += 1 # 如果 word 不存在,会自动创建并赋值为0
print(word_count) # defaultdict(<class ‘int’>, {‘a’: 2, ‘b’: 1, ‘c’: 1})

# 使用 list 作为工厂
list_dict = defaultdict(list)
list_dict[“fruits”].append(“apple”)

七、优先队列

优先队列是一种抽象数据类型,它类似于常规的队列或栈,但每个元素都关联一个“优先级”。出队操作总是移除优先级最高(或最低)的元素,而不是像普通队列那样按先进先出的顺序。

其底层通常由 堆(Heap) 这种数据结构实现,因此插入和删除操作的时间复杂度均为 O(log n),获取最值元素的时间复杂度为 O(1)


C++优先队列

在 C++ 中,优先队列是标准模板库 (STL) 中 queue头文件下的一个容器适配器 (std::priority_queue)。

1. 头文件

#include <queue>

2. 基本声明

std::priority_queue<T> pq; // 默认最大堆,优先级最高的(值最大的)在顶部
std::priority_queue<T, std::vector<T>, std::greater<T>> pq; // 最小堆,值最小的在顶部
  • T: 元素类型,如 int, std::string等。
  • Container: 底层容器,默认为 std::vector<T>。你也可以使用 std::deque<T>,但通常不需要。
  • Compare: 比较函数/函数子,用于定义优先级顺序。默认为 std::less<T>(最大堆)。

3. 主要用法和成员函数

方法功能描述时间复杂度
push(const T& value)将元素插入优先队列O(log n)
emplace(Args&&... args)在队列中原地构造一个新元素(避免复制)O(log n)
pop()移除顶部元素(优先级最高的元素)O(log n)
top()返回顶部元素的常量引用(不移除它)O(1)
empty()检查队列是否为空O(1)
size()返回队列中元素的数量O(1)

注意std::priority_queue没有 clear()方法。如果需要清空,可以用循环 pop或直接重新赋值一个空的队列。

// 清空队列的方法
pq = std::priority_queue<T>(); // 方法一:赋值为一个新的空队列
while (!pq.empty()) pq.pop();  // 方法二:循环弹出

4. 默认最大堆(降序)

#include <iostream>
#include <queue>

int main() {
    std::priority_queue<int> pq; // 最大堆

    pq.push(30);
    pq.push(10);
    pq.push(50);
    pq.push(20);

    while (!pq.empty()) {
        // 输出顺序将是 50, 30, 20, 10
        std::cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}

5. 最小堆(升序)

#include <iostream>
#include <queue>
#include <vector>
#include <functional> // 对于 std::greater

int main() {
    // 必须显式提供底层容器和比较器
    std::priority_queue<int, std::vector<int>, std::greater<int>> pq;

    pq.push(30);
    pq.push(10);
    pq.push(50);
    pq.push(20);

    while (!pq.empty()) {
        // 输出顺序将是 10, 20, 30, 50
        std::cout << pq.top() << " ";
        pq.pop();
    }
    return 0;
}

6. 自定义比较器(存放自定义对象)

这是最灵活和强大的用法。你需要定义一个函数子(Functor)或使用 Lambda 表达式。

#include <iostream>
#include <queue>
#include <string>

// 自定义类
struct Task {
    int priority;
    std::string description;

    Task(int p, const std::string& d) : priority(p), description(d) {}
};

// 方法一:自定义比较函数子(Functor)
// 我们希望优先级数字小的任务反而优先级高(最小堆)
struct CompareTask {
    bool operator()(const Task& a, const Task& b) {
        // 返回 true 表示 a 的优先级应该低于 b(即 a 应该在 b 之后)
        return a.priority > b.priority;
    }
};

int main() {
    // 使用自定义比较器
    std::priority_queue<Task, std::vector<Task>, CompareTask> pq;

    pq.push(Task(3, "Low priority task"));
    pq.push(Task(1, "High priority task"));
    pq.push(Task(2, "Medium priority task"));

    while (!pq.empty()) {
        Task task = pq.top();
        std::cout << task.priority << ": " << task.description << std::endl;
        pq.pop();
    }
    // 输出:
    // 1: High priority task
    // 2: Medium priority task
    // 3: Low priority task

    return 0;
}

使用 Lambda 表达式作为比较器(C++11 及以上)

这种方法需要你在声明时指定比较器的类型,并传入 Lambda。

auto cmp = [](const Task& a, const Task& b) { return a.priority > b.priority; };
// 注意:decltype(cmp) 用于获取 Lambda 的类型
std::priority_queue<Task, std::vector<Task>, decltype(cmp)> pq(cmp);

Python优先队列

Python 的标准库提供了 heapq模块,但它实现的是最小堆。所有功能都是基于列表 (list) 的模块级函数。

1. 头文件(模块)

import heapq

2. 主要函数

函数功能描述时间复杂度
heapq.heappush(heap, item)将元素 item推入堆 heapO(log n)
heapq.heappop(heap)弹出并返回堆中的最小元素O(log n)
heapq.heappushpop(heap, item)先 push item,再 pop 最小元素。比分开调用效率高O(log n)
heapq.heapreplace(heap, item)先 pop 最小元素,再 push item。比分开调用效率高O(log n)
heapq.heapify(x)将常规列表 x在原地转换为堆(最小堆)O(n)
heapq.nlargest(n, iterable)从可迭代对象中返回前 n个最大的元素O(log n)
heapq.nsmallest(n, iterable)从可迭代对象中返回前 n个最小的元素O(log n)

3. 基本最小堆用法

import heapq

# 创建一个空列表来表示堆
min_heap = []
data = [30, 10, 50, 20]

# 将数据推入堆
for item in data:
    heapq.heappush(min_heap, item)

# 或者,也可以直接使用 heapify 转换列表
# heapq.heapify(data) # 此时 data 就变成了堆
# min_heap = data

while min_heap:
    # 按顺序弹出最小元素:10, 20, 30, 50
    smallest = heapq.heappop(min_heap)
    print(smallest, end=' ')

4. 实现最大堆

Python 没有直接的最大堆。通用技巧是存入元素的负数,这样最小的负数就代表了原值中最大的数。

import heapq

# 实现最大堆:存入和取出时都对值取反
max_heap = []
data = [30, 10, 50, 20]

for item in data:
    heapq.heappush(max_heap, -item) # 存入负值

while max_heap:
    # 弹出负值的最小项(即原值的最大项),再取反得到原值
    largest = -heapq.heappop(max_heap)
    print(largest, end=' ') # 输出:50, 30, 20, 10

5. 存放元组(实现多级比较,非常常用)

这是 Python 中优先队列最强大的功能,常用于实现按优先级排序。元组按第一个元素进行比较,如果相同则比较第二个。

import heapq

# 模拟任务,格式:(优先级, 描述)
heap = []
heapq.heappush(heap, (3, "Low priority task"))
heapq.heappush(heap, (1, "High priority task"))
heapq.heappush(heap, (2, "Medium priority task"))

while heap:
    priority, description = heapq.heappop(heap)
    print(f"{priority}: {description}")

# 输出:
# 1: High priority task
# 2: Medium priority task
# 3: Low priority task

如果你想实现最大堆优先级,只需对优先级取负即可:

heapq.heappush(heap, (-priority, description))

6. 自定义类的比较

需要实现 __lt__(小于) 魔术方法,因为 heapq在比较时依赖于它。

import heapq

class Task:
    def __init__(self, priority, description):
        self.priority = priority
        self.description = description

    # 定义小于操作符,使类实例可以相互比较
    # 这里定义成最小堆的逻辑:优先级数字小的任务更小
    def __lt__(self, other):
        return self.priority < other.priority

# 使用方法与普通类型一样
heap = []
heapq.heappush(heap, Task(3, "Low"))
heapq.heappush(heap, Task(1, "High"))
heapq.heappush(heap, Task(2, "Medium"))

while heap:
    task = heapq.heappop(heap)
    print(f"{task.priority}: {task.description}")

八、栈

栈(Stack)是一种遵循 LIFO(Last In, First Out,后进先出) 原则的线性数据结构。这意味着最后一个被添加的元素将会是第一个被移除的元素。其基本操作通常命名为:

  • Push(压栈): 将一个元素添加到栈顶。
  • Pop(出栈): 移除并返回栈顶的元素。
  • Peek / Top(查看栈顶): 返回栈顶的元素但不移除它。
  • isEmpty(判空): 检查栈是否为空。

C++栈

在 C++ 中,最标准、最常用的栈实现是标准模板库(STL)中的 std::stack

1. 实现方式

#include <stack>

std::stack是一个容器适配器(Container Adapter)。这意味着它基于其他底层容器(如 std::deque, std::list, std::vector)来提供栈的接口。默认情况下,它的底层容器是 std::deque

2. 定义与初始化

#include <iostream>
#include <stack>
#include <vector>
#include <list>

int main() {
    // 1. 使用默认底层容器(deque)
    std::stack<int> stack1;

    // 2. 使用特定的底层容器(例如 vector)
    std::stack<int, std::vector<int>> stack2;

    // 3. 使用 list 作为底层容器
    std::stack<int, std::list<int>> stack3;

    // 4. 用一个已存在的容器初始化(C++11 起)
    std::vector<int> vec = {1, 2, 3};
    // 注意:这会按顺序 {1, 2, 3} 压栈,栈顶是 3
    std::stack<int, std::vector<int>> stack4(vec);

    return 0;
}

3. 核心操作

std::stack<int> s;

// Push - 压栈
s.push(10);
s.push(20);
s.push(30); // 栈现在为:顶 [30, 20, 10] 底

// Top - 查看栈顶元素(不删除)
std::cout << "Top element: " << s.top() << std::endl; // 输出 30

// Pop - 弹出栈顶元素(不返回它,仅是删除)
s.pop(); // 移除 30
std::cout << "Top after pop: " << s.top() << std::endl; // 输出 20

// Empty - 检查栈是否为空
if (s.empty()) {
    std::cout << "Stack is empty" << std::endl;
} else {
    std::cout << "Stack is not empty" << std::endl; // 会执行这条
}

// Size - 获取栈中元素个数
std::cout << "Stack size: " << s.size() << std::endl; // 输出 2

4. 重要特性与注意事项

  • pop()不返回元素:这是 C++ STL 设计的一个特点,为了保证异常安全。你需要先用 top()获取元素,然后再 pop()移除它。
  • 底层容器访问受限:你无法直接遍历 std::stack的所有元素(因为这会违反栈的 LIFO 原则)。如果需要遍历,你可能选错了数据结构,或者需要将栈元素弹出到另一个临时栈中。
  • 性能:所有操作(push, pop, top, empty)的时间复杂度都是常数时间 O(1)

Python栈

Python 没有专门的、独立的“栈”类。通常使用列表(list)来模拟栈的行为,或者使用 collections.deque来获得更高效的实现。

1. 使用 List 模拟栈

a. 实现方式

Python 的 list提供了 append()pop()方法,可以非常直观地模拟栈。

b. 用法详列

# 初始化一个空栈
stack = []

# Push - 压栈,使用 append()
stack.append(10)
stack.append(20)
stack.append(30) # 栈现在为:底 [10, 20, 30] 顶
print("Stack after pushes:", stack) # 输出 [10, 20, 30]

# Pop - 弹出栈顶元素(会返回该元素)
top_element = stack.pop()
print("Popped element:", top_element) # 输出 30
print("Stack after pop:", stack)      # 输出 [10, 20]

# Peek / Top - 查看栈顶元素(不删除)
if stack: # 先判断栈是否非空,避免索引错误
    print("Top element:", stack[-1]) # 输出 20

# Empty - 检查栈是否为空
if not stack:
    print("Stack is empty")
else:
    print("Stack is not empty") # 会执行这条

# Size - 获取栈中元素个数
print("Stack size:", len(stack)) # 输出 2

c. 重要特性与注意事项

  • 性能问题:使用 list模拟栈时,append()pop()尾部操作的平均时间复杂度是 O(1)。但在尾部插入时,如果底层内存需要重新分配,可能会触发最坏情况 O(n),但摊销(amortized)后仍是 O(1)。
  • 不要在头部操作:绝对不要使用 list.insert(0, item)list.pop(0)来模拟栈,这些操作的时间复杂度是 O(n),效率极低。

2. 使用 collections.deque实现栈(推荐)

deque(双端队列)是一个双向链表,在两端进行添加或删除操作都具有 O(1) 的性能,是实现栈和队列的理想选择。

a. 用法

from collections import deque

# 初始化一个栈(deque)
stack = deque()

# Push - 压栈,使用 append()
stack.append(10)
stack.append(20)
stack.append(30)

# Pop - 弹出栈顶元素,使用 pop()
top_element = stack.pop() # 30

# Top - 查看栈顶元素
if stack:
    print(stack[-1]) # 20

# 其他操作如判空、大小与 list 相同
print(len(stack) == 0) # 判空
print(len(stack))      # 大小

b. 为什么推荐 deque

  • 性能dequeappend()pop()操作保证是稳定的 O(1) 时间复杂度,没有 list可能触发的内存重新分配问题。
  • 明确意图:当你使用 deque并只在一端操作时,代码的阅读者能清晰地明白你在使用一个栈(或队列),而不是一个通用的列表。

九、队列


C++ 队列

C++ 标准库提供了 std::queue容器适配器,它通常基于 std::dequestd::list实现,提供了严格的 FIFO(先进先出)接口。

1.核心头文件和类型

#include <queue> // 主要头文件
#include <deque> // 底层容器
#include <list>  // 可选的底层容器

std::queue<int> q; // 默认基于 deque
std::queue<int, std::list<int>> q_list; // 指定基于 list

2. 详细用法列表

操作函数描述时间复杂度
入队push(const T& value)将元素添加到队列末尾O(1)
出队pop()移除队首元素。注意:不返回该元素!O(1)
访问队首front()返回队首元素的引用(可读可写)O(1)
访问队尾back()返回队尾元素的引用(可读可写)O(1)
判空empty()检查队列是否为空O(1)
获取大小size()返回队列中元素的数量O(1)
构造queue<T> q;构造一个空队列-
构造queue<T> q(other_q);拷贝构造O(n)
移动构造queue<T> q(move(other_q));移动构造(C++11)O(1)
赋值q1 = q2拷贝赋值O(n)
移动赋值q1 = move(q2)移动赋值(C++11)O(1)
交换swap(q1, q2)交换两个队列的内容O(1)

3. 重要注意事项

  1. pop()不返回值:这是 C++ std::queue与许多其他语言队列最大的区别。你必须先使用 front()获取队首元素,然后再 pop()将其移除,以避免元素在拷贝或抛出异常时丢失。

    std::queue<int> q;
    q.push(1);
    q.push(2);
    
    // 正确用法
    int front_element = q.front(); // 获取队首元素 1
    q.pop();                       // 移除队首元素
    
    // 错误!pop() 返回 void,不能这样用
    // int element = q.pop();
    
  2. 底层容器std::queue是一个容器适配器。你可以指定第二个模板参数来改变其底层容器(必须提供 back(), push_back(), pop_front(), empty(), size()等接口)。

    #include <list>
    std::queue<int, std::list<int>> q; // 基于 list 的队列
    
  3. 线程安全std::queue本身不是线程安全的。如果需要在多线程环境中使用,需要自行加锁(如 std::mutex),或者使用像 tbb::concurrent_queue这样的第三方并发容器。

4. 示例代码

#include <iostream>
#include <queue>

int main() {
    std::queue<std::string> customer_queue;

    // 入队
    customer_queue.push("Alice");
    customer_queue.push("Bob");
    customer_queue.push("Charlie");

    // 查看队首和队尾
    std::cout << "Front: " << customer_queue.front() << std::endl; // Alice
    std::cout << "Back: " << customer_queue.back() << std::endl;  // Charlie

    // 遍历并出队(遍历后会清空队列)
    while (!customer_queue.empty()) {
        std::cout << "Processing: " << customer_queue.front() << std::endl;
        customer_queue.pop();
    }
    // 现在 size() 为 0

    return 0;
}

Python队列

Python 的队列实现更加多样化,根据不同的使用场景,主要有以下三种:

1. collections.deque(双端队列)

这是最常用、最通用的队列实现,位于标准库的 collections模块。它是一个双向队列,在两端都能实现 O(1) 时间复杂度的添加和删除操作。

核心头文件

from collections import deque

详细用法列表

操作方法描述时间复杂度
入队 (右)append(item)将元素添加到队列右端(尾部)O(1)
出队 (左)popleft()移除并返回队首(左端)元素O(1)
入队 (左)appendleft(item)将元素添加到队列左端(头部)O(1)
出队 (右)pop()移除并返回队尾(右端)元素O(1)
判空if not dq:借助 len()或直接判断布尔值O(1)
获取大小len(dq)返回队列中元素的数量O(1)
访问队首dq[0]访问但不移除队首元素O(1)
访问队尾dq[-1]访问但不移除队尾元素O(1)
构造dq = deque()构造一个空队列-
构造dq = deque(iterable)从可迭代对象(如列表)构造队列O(n)
扩展队列extend(iterable)在右侧批量添加元素O(k)
扩展队列extendleft(iterable)在左侧批量添加元素(注意顺序)O(k)
旋转rotate(n=1)将队列向右旋转 n 步(负数为向左)O(k)
清除clear()移除所有元素O(n)

示例代码

from collections import deque

# 创建队列
q = deque(['Alice', 'Bob']) 
# 或者 q = deque(); q.append('Alice')

# 入队
q.append('Charlie')   # 右端入队: deque(['Alice', 'Bob', 'Charlie'])
q.appendleft('Zoe')   # 左端入队: deque(['Zoe', 'Alice', 'Bob', 'Charlie'])

# 访问
print(q[0])     # 队首: 'Zoe'
print(q[-1])    # 队尾: 'Charlie'

# 出队
front = q.popleft() # 移除并返回 'Zoe'
rear = q.pop()      # 移除并返回 'Charlie'

# 遍历(不会清空队列)
for person in q:
    print(person) # 现在队列里是 ['Alice', 'Bob']

2. queue.Queue(同步队列)

这个模块专门为多线程编程设计。它提供了线程安全的 FIFO 实现,支持基本的阻塞操作,用于在生产者和消费者线程之间安全地传递消息。

核心头文件

import queue

详细用法列表

操作方法描述
入队put(item, block=True, timeout=None)放入元素。可选阻塞和超时。
出队get(block=True, timeout=None)移除并返回一个元素。可选阻塞和超时。
判空empty()检查队列是否为空(不可靠,因多线程环境瞬息万变)
获取大小qsize()返回队列的大致大小(同样不可靠
任务完成task_done()消费者线程告知队列一个任务已完成
等待完成join()阻塞主线程,直到队列中所有任务被处理完毕

示例代码 (多线程)

import queue
import threading
import time

def worker(q):
    while True:
        item = q.get() # 阻塞获取任务
        print(f"Processing: {item}")
        time.sleep(1)
        q.task_done() # 告知任务完成

# 创建线程安全队列
task_queue = queue.Queue()

# 启动工作线程
threading.Thread(target=worker, args=(task_queue,), daemon=True).start()

# 主线程作为生产者
for i in range(5):
    task_queue.put(f"Task {i}")

task_queue.join() # 等待所有任务完成
print("All tasks done!")

3. queue.LifoQueuequeue.PriorityQueue

  • queue.LifoQueue:一个线程安全的后进先出队列(栈)。
  • queue.PriorityQueue:一个线程安全的优先级队列。元素通常为 (priority, data)元组,优先级数字越小越先出队。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值