【C++】深入理解vector(1):vector的使用和OJ题
目录
一 vector的介绍
1 什么是vector
2 vector的介绍
二 vector的使用
1 vector的构造
2 没有输入输出流,无法cout,自己写
3 vector扩容问题
4 vector的增删查改
三 vector在OJ中的使用
1 只出现一次的数字
2 杨辉三角
一 vector的介绍
1 什么是vector
vector这个单词的意思是向量,但是在STL中的使用可以理解为顺序表
string是字符串,vector则是一个改变数据的顺序容器
2 vector的介绍
vector的文本介绍
这段话的意思如下:
代表大小可以改变的数组。
和数组一样,向量的元素存储在连续的内存位置,这意味着可以通过指向元素的常规指针进行偏移来访问其元素,并且访问效率与数组相同。但与数组不同的是,向量的大小可以动态改变,其存储空间由容器自动管理。
在内部,向量使用动态分配的数组来存储元素。当插入新元素时,为了增大数组的大小,可能需要重新分配该数组,这意味着要分配一个新数组并将所有元素移动到新数组中。就处理时间而言,这是一项相对开销较大的任务,因此,向量不会在每次向容器中添加元素时都进行重新分配。
相反,向量容器可能会分配一些额外的存储空间以应对可能的增长,因此容器的实际容量可能会大于严格容纳其元素所需的存储空间(即其大小)。不同的库可以实现不同的增长策略,以平衡内存使用和重新分配的开销,但无论如何,重新分配应该只在大小呈对数增长的间隔发生,这样才能保证在向量末尾插入单个元素的时间复杂度为均摊常数时间(参见 push_back 操作)。
二 vector的使用
先包含头文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
using namespace std;
1 vector的构造
(constructor)构造函数申明 | 接口说明 |
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构 造 |
void test_vector1()
{vector<int> v1;vector<int> v2(10, 1);vector<int> v3(v2.begin(), v2.end());string s1("xxxxxxxxxxxxx");vector<int> v4(s1.begin(), s1.end());vector<int> v5(v3);//vector<int> v6({ 1,2,3,4,5 });vector<int> v6 = { 1,2,3,4,5 };vector<int> v7 = { 1,2,3,4,5,1,1,1,1,1,1 };
}
该函数展示了 vector<int>
的 6 种初始化方式,覆盖了常见场景:
初始化方式 | 代码示例 | 说明 |
---|---|---|
默认初始化 | vector<int> v1; | 创建空 vector,无元素(size() 为 0)。 |
填充初始化 | vector<int> v2(10, 1); | 创建包含 10 个元素的 vector,每个元素值为 1。 |
迭代器范围初始化 | vector<int> v3(v2.begin(), v2.end()); | 用另一个容器(v2 )的迭代器范围 [begin, end) 初始化,相当于复制 v2 的所有元素(v3 与 v2 内容相同)。 |
跨容器迭代器初始化 | vector<int> v4(s1.begin(), s1.end()); | 用 string 的迭代器范围初始化(string 本质是字符序列),每个字符的 ASCII 码会被转换为 int 存储(例如 'x' 的 ASCII 码是 120,所以 v4 的元素全为 120)。 |
拷贝构造初始化 | vector<int> v5(v3); | 用另一个 vector(v3 )拷贝初始化,v5 与 v3 元素完全相同。 |
初始化列表初始化 | vector<int> v6 = {1,2,3,4,5}; | C++11 新增,用初始化列表 {} 直接指定元素,最直观(等价于 vector<int> v6({1,2,3,4,5}); )。 |
2 没有输入输出流,无法cout,自己写
我们需要自己写一个print函数,来完成流提取和流输出
void Print(const vector<int>& v)
{for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}
在
vector<int>
中,<int>
表示模板参数,用于指定std::vector
容器所存储的元素类型。这里的<int>
说明这个vector
只能存储int
(整数)类型的元素。
我们也可以用范围for来写
//范围forfor (auto e : v){cout << e << " ";}cout << endl;
for (auto e : v)
表示 “对于容器 v
中的每个元素,依次赋值给变量 e
,执行循环体”。
3 vector扩容问题
void test_vector2()
{vector<int> v1;const int n = 100;size_t old = v1.capacity();for (size_t i = 0; i < n; i++){v1.push_back(i);if (old != v1.capacity()){cout << v1.capacity() << endl;old = v1.capacity();}}
}
我们发现,在Vs下,扩容是以1.5倍增长,而在g++下,扩容是以2倍增长
如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够,这样就可以避免边插入边扩容导致效率低下
void test_vector2()
{vector<int> v1;const int n = 100;v1.reserve(n);size_t old = v1.capacity();size_t begin = clock();for (size_t i = 0; i < n; i++){v1.push_back(i);if (old != v1.capacity()){cout << v1.capacity() << endl;old = v1.capacity();}}
}
4 vector的增删查改
接口 | 使用示例 | 功能效果 | 注意要点 |
---|---|---|---|
push_back | vector<int> v; v.push_back(10); | 在 vector 末尾添加元素 10 ,若容量不足会扩容,导致 size 加 1。 | 扩容时可能重新分配内存,使所有迭代器失效;元素是拷贝或移动(依元素类型和 C++ 版本)。 |
pop_back | vector<int> v{1,2,3}; v.pop_back(); | 删除 vector 末尾的元素 3 ,size 减 1。 | 不能对空 vector 调用,否则行为未定义;只删除元素,不释放内存(容量不变)。 |
find | vector<int> v{1,2,3}; auto it = find(v.begin(), v.end(), 2); | 在 v.begin() 到 v.end() 范围内查找值为 2 的元素,返回指向该元素的迭代器,若找不到返回 v.end() 。 | 属于算法库函数,需包含 <algorithm> ;只支持顺序查找,时间复杂度 O (n)。 |
insert | vector<int> v{1,3}; v.insert(v.begin() + 1, 2); | 在 v 的第 2 个位置(下标为 1)插入元素 2 ,size 加 1。 | 插入位置及之后的元素后移,若容量不足会扩容,导致插入点到末尾的迭代器失效;元素是拷贝或移动。 |
erase | vector<int> v{1,2,3,4}; v.erase(v.begin() + 1); v.erase(v.begin(), v.begin() + 2); | 第一个示例:删除下标为 1 的元素 2 ;第二个示例:删除从下标 0 到 1 的元素 1 、2 。 | 被删元素之后的迭代器失效;返回指向下一个元素的迭代器;只删除元素,不释放内存。 |
swap | vector<int> v1{1,2}, v2{3,4}; v1.swap(v2); | 交换 v1 和 v2 的数据空间,v1 变为 {3,4} ,v2 变为 {1,2} 。 | 是高效操作,仅交换内部指针等,时间复杂度 O (1);swap 后迭代器仍有效,但指向另一容器的元素。 |
operator[] | vector<int> v{1,2,3}; int num = v[1]; v[0] = 10; | 像数组一样,通过下标 1 获取元素 2 ;通过下标 0 将元素修改为 10 。 |
我们来模拟实现一下:
void test_vector3()
{vector<int> v1 = {1, 2, 3, 4, 5};v1.push_back(6);Print(v1); // 输出:1 2 3 4 5 6 v1.insert(v1.begin(), 0); // 在迭代器 v1.begin() 位置(头部)插入 0Print(v1); // 输出:0 1 2 3 4 5 6 v1.insert(v1.begin() + 3, 0); // 在下标为 3 的位置前插入 0Print(v1); // 输出:0 1 2 0 3 4 5 6v1.erase(v1.begin()); // 删除迭代器 v1.begin() 指向的元素(头部的 0)Print(v1); // 输出:1 2 0 3 4 5 6 v1.erase(v1.begin() + 3); // 删除下标为 3 的元素(值为 3)Print(v1); // 输出:1 2 0 4 5 6
}
注意:
和string一样,vector也只有尾插和尾删,没有头插和头删,因为要挪动数据,效率太。
如果要头插、或者头删和string一样,用insert和erase(如上述代码所示)。
三 vector在OJ中的使用
1 只出现一次的数字
力扣链接:只出现一次的数字
解题思路:
利用异或运算的性质:
- 任何数和 0 进行异或运算,结果还是这个数本身,即 a⊕0=a。
- 任何数和它本身进行异或运算,结果是 0,即 a⊕a=0。
- 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕(a⊕a)=b⊕0=b。
因为数组中除了一个元素只出现一次,其余每个元素都出现两次,所以将数组中所有元素依次进行异或运算,最后剩下的结果就是那个只出现一次的元素。
我们来演示一下:
举个具体的例子,比如数组是 [4,1,2,1,2]
:
- 一开始
value = 0
。 - 第一次异或:
0 ^ 4 = 4
,value
变成4
。 - 第二次异或:
4 ^ 1 = 5
,value
变成5
。 - 第三次异或:
5 ^ 2 = 7
,value
变成7
。 - 第四次异或:
7 ^ 1 = 6
,value
变成6
。 - 第五次异或:
6 ^ 2 = 4
,value
变成4
。
你看,最后 value
就是 4
,也就是数组中只出现一次的那个数
但是有uu可能有疑问:为什么4和1异或之后变成了5:
初始时 value = 0
(二进制是 0000
)。
第一次异或:value ^ 4
4
的二进制是0100
。0000 ^ 0100 = 0100
(也就是十进制的4
),所以此时value = 4
。
第二次异或:value ^ 1
(此时 value
是 4
,即 0100
)
1
的二进制是0001
。- 逐位异或:
- 第 1 位(从右数,最低位):
0 ^ 1 = 1
- 第 2、3 位:
0 ^ 0 = 0
,1 ^ 0 = 1
- 第 4 位及以上:
0 ^ 0 = 0
- 第 1 位(从右数,最低位):
- 结果二进制是
0101
,也就是十进制的5
。所以此时value
变成5
。
代码实现如下:
class Solution {
public:int singleNumber(vector<int>& nums) {int value=0;for(auto i :nums){value ^=i;cout<<endl;}return value;}
};
2 杨辉三角
是一个不规则的二维数组,每一行的第一个和最后一个都是1
思路一:
用C语言实现。但是用C语言实现就需要开辟二维数组,而且还是指针数组,就会很麻烦
思路二:用vector解决
那用vector怎么表示二维数组呢?vector<vector<int>>
代码实现如下:
class Solution {
public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv;//定义行vv.resize(numRows,vector<int>());//定义列for(size_t i = 0;i < numRows;++i){vv[i].resize(i + 1,1);//1}for(size_t i = 2;i < vv.size();++i)//第0、1行的都不需要处理,1{for(size_t j = 1;j < vv[i].size() - 1;++j){vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}return vv;}
};