Vector
- 增:在末尾添加元素是O(1)(平均情况),但在中间或开头添加元素是O(n),因为可能需要移动元素以腾出空间。
- 删:在末尾删除元素是O(1)(平均情况),但在中间或开头删除元素是O(n),因为需要移动元素来填补空缺。
- 查:通过迭代器或索引访问是O(1)。
- 改:通过迭代器或索引修改是O(1)。
#include <iostream>
#include <vector>
int main()
{
// 初始化
std::vector<int> vec = {1, 2, 3, 4, 5};
// 增加元素
vec.push_back(6);
// 删除元素
vec.erase(vec.begin() + 2); // 删除第三个元素
// 查找元素
auto it = std::find(vec.begin(), vec.end(), 4);
if (it != vec.end())
{
std::cout << "Found 4 at position: " << std::distance(vec.begin(), it) << std::endl;
}
// 修改元素
vec[0] = 0;
// 打印修改后的vector
for (int n : vec)
{
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在C++中,
std::vector是一个序列容器,它支持随机访问迭代器,允许在容器中进行快速的元素访问。如果你想要将一个std::vector(比如s1)的所有元素追加到另一个std::vector(比如s2)中,你可以使用几种不同的方法。
方法1:使用insert成员函数 你可以使用insert成员函数,将s1的开始迭代器到结束迭代器范围内的所有元素插入到s2的末尾。但是,由于insert在vector中可能会导致内存重新分配和元素移动,因此这可能不是最高效的方法,特别是对于大型vector。
s2.insert(s2.end(), s1.begin(), s1.end());
方法2:使用std::copy或std::move(对于大型vector,推荐使用std::move) 对于大型vector,如果你不关心s1在追加操作后的内容(即你可以清空或重新使用s1),那么使用std::move可以更加高效,因为它允许元素以“移动”而非“复制”的方式被传输。然而,请注意,std::move后,s1中的元素将处于未定义状态(通常是有效的,但值不确定)。
// 使用std::move(如果s1之后不再需要保留原数据)
s2.insert(s2.end(), std::make_move_iterator(s1.begin()), std::make_move_iterator(s1.end()));
// 或者,如果你只是想要复制,可以使用std::copy(但注意,这可能会比std::move慢)
std::copy(s1.begin(), s1.end(), std::back_inserter(s2));
// 如果你确定不再需要s1中的数据,并且想要清空s1以节省内存
s1.clear(); // 在std::move之后,这一步是可选的,但推荐
方法3:简单循环(不推荐,除非有特殊需求) 尽管这不是最高效的方法,但你也可以通过简单地遍历s1并使用push_back将每个元素添加到s2中来追加元素。然而,这种方法在处理大型vector时可能会非常慢,因为它需要逐个元素地复制(或移动)并可能多次重新分配s2的内存。
for (const auto& elem : s1)
{
s2.push_back(elem);// 对于大型vector,这通常不是最高效的方法
}
List
- 增:在任意位置添加元素是O(1),因为list是双向链表。
- 删:在任意位置删除元素是O(1)。
- 查:遍历查找是O(n),但如果你已经有了指向元素的迭代器,则访问是O(1)。
- 改:通过迭代器修改是O(1)。
#include <iostream>
#include <list>
int main()
{
// 初始化
std::list<int> lst = {1, 2, 3, 4, 5};
// 增加元素
lst.push_front(0); // 在开头添加
lst.push_back(6); // 在末尾添加
// 删除元素
lst.erase(lst.begin() + 2); // 删除第三个元素
// 查找元素
auto it = std::find(lst.begin(), lst.end(), 4);
if (it != lst.end())
{
std::cout << "Found 4" << std::endl;
}
// 修改元素
auto it_to_modify = lst.begin();
std::advance(it_to_modify, 2); // 移动到第三个元素
*it_to_modify = 30;
// 打印修改后的list
for (int n : lst)
{
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在C++中,
std::list是一个双向链表,它支持在容器中的任何位置进行快速插入和删除操作。如果你想要将一个std::list(比如s1)的所有元素追加到另一个std::list(比如s2)中,你可以使用splice成员函数,这是std::list特有的一个非常高效的方法,因为它可以在常数时间内完成操作,而不需要逐个元素地复制或移动。 以下是一个示例,展示了如何将s1的所有元素追加到s2中:
#include <iostream>
#include <list>
int main()
{
// 初始化两个list
std::list<int> s1 = {1, 2, 3};
std::list<int> s2 = {4, 5, 6};
// 将s1的所有元素追加到s2中
// 使用splice成员函数,这是最高效的方法
s2.splice(s2.end(), s1);
// 此时,s1变为空,s2包含了s1和s2的原始元素
// 打印s2以查看结果
for (int elem : s2)
{
std::cout << elem << " ";
}
std::cout << std::endl;
// 注意:此时s1是空的,如果你还想查看s1,可以在splice之前保存它的一个副本
return 0;
}
Deque
- 增:在两端添加元素是O(1),但在中间添加元素是O(n),因为可能需要重新分配内部数组并移动元素。
- 删:在两端删除元素是O(1),但在中间删除元素是O(n)。
- 查:通过迭代器或索引访问是O(1)。
- 改:通过迭代器或索引修改是O(1)。
#include <iostream>
#include <deque>
int main()
{
// 初始化
std::deque<int> deq = {1, 2, 3, 4, 5};
// 增加元素
deq.push_front(0);
deq.push_back(6);
// 删除元素
deq.erase(deq.begin() + 2);
// 查找元素
auto it = std::find(deq.begin(), deq.end(), 4);
if (it != deq.end())
{
std::cout << "Found 4" << std::endl;
}
// 修改元素
deq[0] = -1;
// 打印修改后的deque
for (int n : deq)
{
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在C++中,
std::deque(双端队列)是一个序列容器,它支持在容器的两端进行快速插入和删除操作。与std::vector相比,std::deque在容器的前端插入或删除元素时更加高效,因为它不是连续存储的。 如果你想要将一个std::deque(比如s1)的所有元素追加到另一个std::deque(比如s2)中,你可以使用insert成员函数,但更常用的方法是直接使用赋值操作符或assign成员函数与迭代器范围,或者std::copy(如果你不想改变s2原有的元素,只想将s1的元素追加到s2的末尾)。然而,对于追加操作,最直接且常用的方法是使用std::deque的insert成员函数配合迭代器或back_inserter迭代器。但更简单的是,如果你只是想简单地将s1的内容添加到s2的末尾,可以直接使用push_back循环或std::copy。 不过,由于std::deque没有直接支持将一个deque的所有元素作为一个整体追加到另一个deque的末尾的成员函数,我们通常使用以下方法之一:
//使用insert和end()迭代器
s2.insert(s2.end(), s1.begin(), s1.end());
//使用`std::copy
#include <algorithm> // 对于std::copy
#include <iterator> // 对于std::back_inserter
std::copy(s1.begin(), s1.end(), std::back_inserter(s2));
//循环使用push_back
for (auto& elem : s1)
{
s2.push_back(elem);
}
Set
-
增(插入):
时间复杂度:平均情况下是 O(log n),最坏情况下也是 O(log n)(尽管在最极端的情况下,如树完全不平衡时,时间复杂度可能接近 O(n),但红黑树的自平衡特性会尽量避免这种情况)。这里的 n 是 set 中元素的数量。 操作:使用 insert 成员函数向 set 中插入元素。如果元素已经存在,则不会插入重复的元素。
-
删(删除):
时间复杂度:与插入操作相同,平均情况下是 O(log n),最坏情况下也是 O(log n)。 操作:可以使用 erase 成员函数来删除 set 中的元素。你可以通过迭代器或键来删除元素。
-
查(查找):
时间复杂度:同样,平均情况下是 O(log n),最坏情况下也是 O(log n)。 操作:使用 find 成员函数来查找元素是否存在于 set 中。如果元素存在,该方法返回指向该元素的迭代器;如果元素不存在,则返回指向 set 尾部的迭代器(即 end() 迭代器)。
-
改(修改):
需要注意的是,set 中的元素是唯一的,并且是通过其键值来标识的。因此,在 set 中“修改”一个元素的概念并不直接适用,因为如果你尝试插入一个已存在的键值,该操作将不会执行任何插入(即不会修改集合)。但是,如果你想要“替换”一个元素的值(尽管这在 set 的上下文中并不常见,因为 set 主要用于存储唯一的键),你需要先删除旧元素,然后插入新元素。 如果你的意图是改变元素的排序顺序(即改变比较函数或对象),那么这通常涉及到创建一个新的 set 并使用新的比较规则来重新插入元素,因为 set 的内部排序是固定的,并且不允许直接修改元素的顺序。
#include <iostream>
#include <set>
int main()
{
// 初始化
std::set<int> s = {1, 2, 3, 4, 5};
// 增加元素(自动去重和排序)
s.insert(6);
// 删除元素
s.erase(3);
// 查找元素
auto it = s.find(4);
if (it != s.end())
{
std::cout << "Found 4" << std::endl;
}
// 注意:set不支持直接通过下标修改元素
// 打印set
for (int n : s)
{
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在C++中,
std::set是一个基于红黑树实现的关联容器,它存储的元素是唯一的,并且这些元素会根据指定的排序准则进行自动排序。如果你想要将一个std::set(比如s1)的所有元素追加到另一个std::set(比如s2)中,你可以使用insert成员函数。由于std::set中的元素是唯一的,因此如果s1中的某个元素已经存在于s2中,那么该元素就不会被再次插入。以下是一个简单的示例,展示了如何将一个std::set(s1)的所有元素追加到另一个std::set(s2)中:
#include <iostream>
#include <set>
int main()
{
// 初始化两个set
std::set<int> s1 = {1, 2, 3};
std::set<int> s2 = {3, 4, 5};
// 将s1的所有元素追加到s2中
// 注意:如果s1中的某个元素已经存在于s2中,则不会被再次插入
for (int elem : s1)
{
s2.insert(elem);
}
// 或者,更简洁地,你可以使用insert的范围版本(C++11及以上)
s2.insert(s1.begin(), s1.end());
// 打印s2以查看结果
for (int elem : s2)
{
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
Map
- 增:插入新元素是O(log n),因为它们是基于红黑树实现的。
- 删:删除元素是O(log n)。
- 查:查找元素是O(log n)。
- 改:修改已存在元素的键是未定义的(因为键是唯一的),但修改值通常是O(log n)(找到元素后修改)。
#include <iostream>
#include <map>
#include <string>
int main()
{
// 初始化
std::map<std::string, int> m = {{"one", 1}, {"two", 2}, {"three", 3}};
// 增加元素
m["four"] = 4;
// 另一种增加元素的方式(如果键已存在,则更新其值)
m.insert(std::make_pair("five", 5));
// 删除元素
m.erase("three");
// 查找元素
auto it = m.find("two");
if (it != m.end())
{
std::cout << "Found 'two' with value: " << it->second << std::endl;
}
// 修改元素
m["one"] = 10; // 如果键存在,则更新其值
// 遍历map
for (const auto& pair : m)
{
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 尝试使用不存在的键(这将不会插入新元素,但可以使用下标操作符来“获取”默认值)
// 注意:如果map之前没有这个键,这将创建一个新的键值对,键为"six",值为0(int的默认构造函数)
// 如果不希望这种自动插入的行为,应该使用find()
int value = m["six"]; // 这里value将是0,因为"six"不在map中,但使用下标访问会默认插入
if (m.find("six") == m.end()) {
std::cout << "'six' was not found in the map before this line, but we inserted it with a value of 0 by accident." << std::endl;
}
// 更安全的获取元素值(不会插入新元素)
auto search = m.find("seven");
if (search != m.end())
{
std::cout << "Found 'seven' with value: " << search->second << std::endl;
} else {
std::cout << "'seven' was not found in the map." << std::endl;
}
return 0;
}
在C++中,
std::map是一个关联容器,它存储的元素是键值对,并且这些元素会根据其键来自动排序。如果你想要将一个std::map(比如s1)的所有元素追加到另一个std::map(比如s2)中,有几种方法可以实现这一点。但是,需要注意的是,由于std::map的元素是基于键来排序的,因此当你将s1的元素插入到s2中时,这些元素将根据s2的排序准则(通常是键的比较函数)重新排序。
以下是一个简单的示例,展示了如何将一个std::map(s1)的所有元素追加到另一个std::map(s2)中:
#include <iostream>
#include <map>
int main() {
// 初始化两个map
std::map<int, std::string> s1 = {{1, "one"}, {2, "two"}, {3, "three"}};
std::map<int, std::string> s2 = {{4, "four"}, {5, "five"}};
// 将s1的所有元素追加到s2中
// 方法1:使用insert_or_assign(C++17及以上)
// 注意:这实际上会基于s2的排序准则重新插入元素,如果键已存在,则更新其值
for (const auto& pair : s1)
{
s2.insert_or_assign(pair.first, pair.second);
}
// 或者,如果你只是想插入而不关心键是否已存在(这将不会更新已存在的键的值)
// 方法2:使用insert
// 注意:这里演示的是如果不用insert_or_assign,而是用insert,并处理可能的插入失败(即键已存在的情况)
// 但对于简单的追加操作,直接使用insert_or_assign或insert通常就足够了
for (const auto& pair : s1)
{
auto result = s2.insert(pair);
if (!result.second)
{
// 如果插入失败(即键已存在),可以选择忽略或做其他处理
// 这里我们选择忽略
}
}
// 打印s2以查看结果
for (const auto& pair : s2)
{
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
stack
类支持后进先出 (LIFO) 数据结构。 可以在脑海中将其类比为一摞盘子。 元素(盘子)只能从堆栈顶部(基容器末尾的最后一个元素)插入、检查或删除。 限制仅访问顶部元素是使用
stack类的原因。
// stack_pop.cpp
// compile with: /EHsc
#include <stack>
#include <iostream>
int main( )
{
using namespace std;
stack <int> s1, s2;
s1.push( 10 );
s1.push( 20 );
s1.push( 30 );
stack <int>::size_type i;
i = s1.size( );
cout << "The stack length is " << i << "." << endl;
i = s1.top( );
cout << "The element at the top of the stack is "
<< i << "." << endl;
s1.pop( );
i = s1.size( );
cout << "After a pop, the stack length is "
<< i << "." << endl;
i = s1.top( );
cout << "After a pop, the element at the top of the stack is "
<< i << "." << endl;
}
queue
queue类支持先进先出 (FIFO) 数据结构。 可以在脑海中将其类比为排队等候银行柜员的人。 元素(人)可从行的后部添加,并且可以从行的前部删除。 行的前部和后部都可以插入。 以这种方式限制仅访问front和back元素是使用queue类的原因。
// queue_pop.cpp
// compile with: /EHsc
#include <queue>
#include <iostream>
int main( )
{
using namespace std;
queue <int> q1, s2;
q1.push( 10 );
q1.push( 20 );
q1.push( 30 );
queue <int>::size_type i;
i = q1.size( );
cout << "The queue length is " << i << "." << endl;
int& ii = q1.back( );
cout << "The integer at the back of queue q1 is " << ii
<< "." << endl;
i = q1.front( );
cout << "The element at the front of the queue is "
<< i << "." << endl;
q1.pop( );
i = q1.size( );
cout << "After a pop the queue length is "
<< i << "." << endl;
i = q1. front ( );
cout << "After a pop, the element at the front of the queue is "
<< i << "." << endl;
}
unordered_map 和 unordered_multimap
- 增:平均情况下是O(1),但在最坏情况下(所有元素都映射到同一个哈希桶)是O(n)。
- 删:平均情况下是O(1),但在最坏情况下是O(n)。
- 查:平均情况下是O(1),但在最坏情况下是O(n)。
- 改:修改已存在元素的值是O(1)(找到元素后修改),但修改键可能导致重新哈希和可能的元素移动,这通常是未定义或不建议的。