目录
🙊 vector 的介绍及使用 🙊
💖 vector 的介绍
- vector 是表示可变大小数组的序列容器。
- 就像数组一样,vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小。
- vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deque, list and forward_list), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 list 和 forward_list 统一的迭代器和引用更好
💖 vector 的使用
💖 vector 的结构
vector 的结构如下:
template < class T, class Alloc = allocator<T> > class vector; // generic template
class Alloc = allocator 是一个空间配置器,第一个模板参数就是 vector 的数据类型。
💖 vector 的成员函数介绍
vector 一共有 6 个默认的成员函数分别是
1、迭代器
2、capacity 相关的成员函数
3、访问数据相关的成员函数
4、修改相关的成员函数
vector 和 string 的区别是 string 只管理字符类型的数组,而 vector 可以管理任意类型的数组。
💖 vector 的常用接口
💖 vector 构造函数
vector 的常用接口如下:
其中第一个是全缺省的函数接口,使用全缺省可以用自己写的空间配置器传参,一般不需要管,只将其当作无参的函数即可。
第二个是用 n 个 value 初始化,value_type 是 vector 里面存的数据类型,size_type 是一个无符号整型。
第三个是以一段迭代器区间初始化。
第四个是拷贝构造
💖 vector 的析构函数
析构函数自动调用,对里面的空间进行释放。
~vector();
💖 vector 的赋值
赋值完成的是一个深拷贝。
vector& operator= (const vector& x);
💖 insert 用法
vector 使用迭代器去实现 insert,在某个位置插入 val,插入一个迭代器区间等。
💖 erase 用法
erase 也是使用迭代器实现
💖 vector 常见的遍历方式
如下使用了三种方式遍历 vector
void test_vector1()
{
//类模板的显示实例化,调用无参的构造函数创建 v 对象
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//遍历 vector
//使用 operator[]
for (size_t i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;
//使用迭代器
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//使用范围 for
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_vector1();
return 0;
}
拷贝构造函数的使用方法如下:
vector<int> copy(v);
for (auto e : copy)
{
cout << e << " ";
}
cout << endl;
也可以使用 n 个 value 进行构造:
explicit vector (size_type n, const value_type& val = value_type();
使用方式如下:
void test_vector2()
{
//使用10个1对v1进行初始化
vector<int> v1(10, 1);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
还可以使用迭代器区间构造,这里的迭代器是一个模板,除了传 vector 容器的迭代器之外,还可以传其他容器的迭代器,
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
使用方式如下:
void test_vector2()
{
//使用10个1对v1进行初始化
vector<int> v1(10, 1);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(v1.begin(), v1.end());
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//只要类型匹配,就是string容器里面的数据能够存到vector容器里面,就可以这样使用
//这里string里面存的是char类型的数据,而char类型可以赋值给int类型
string s1("hello world");
vector<int> v3(s1.begin(), s1.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
也可以用迭代器实现反向遍历
void test_vector3()
{
//类模板的显示实例化,调用无参的构造函数创建 v 对象
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//reverse_iterator 是通过类模板实例化一个类的反向迭代器类型
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
}
cout << endl;
}
💖 vector 空间增长问题
- capacity 的代码在 vs 和 g++ 下分别运行会发现,vs 下 capacity 是按 1.5 倍增长的,g++ 是按 2 倍增长的。这个问题经常会考察,不要固化的认为,vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的。vs 是 PJ 版本 STL,g++ 是 SGI 版本 STL。
- reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。
- resize 在开空间的同时还会进行初始化,影响 size。
测试代码如下:
// 测试vector的默认扩容机制
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141
g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
💖 resize 的用法
resize 就是开空间加初始化
void test_vector4()
{
vector<int> v;
v.resize(10, 0);
}
🙊 vector 例题 🙊
💖 只出现一次的数字
题目描述如下:
代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for(auto e:nums)
{
//用异或的方式,任何数与0异或就是任何数
ret ^= e;
}
return ret;
}
};
💖 杨辉三角
给定一个非负整数 numRows,生成 「杨辉三角」 的前 numRows 行。
在 「杨辉三角」 中,每个数是它左上方和右上方的数的和。
注意这里对 vector < vector < int >> 进行相关解释说明:
代码如下:
class Solution {
public:
vector<vector<int>> generate(int numRows) {
//定义一个对象vv
vector<vector <int>> vv;
//需要 numRows 行,每一行的每一个对象是 vector<int>
//所以这里开辟了 numRows 个空间,用匿名对象 vector<int>() 进行初始化
vv.resize(numRows,vector<int>());
//这里的 vv.size() 代表这个杨辉三角一共有多少行。用下标 i 来控制每一行
for(size_t i = 0; i < vv.size(); ++i)
{
//每一行的数据有 i+1 个
vv[i].resize(i+1,0);
//每一行的第一个和最后一个改成1
vv[i][0] = vv[i][vv[i].size()-1] = 1;
}
//遍历二维数组
for(size_t i = 0; i < vv.size(); ++i)
{
for(size_t j = 0; j < vv[i].size(); ++j)
{
//调用两次[]
if(vv[i][j] == 0)
{
vv[i][j] = vv[i-1][j]+vv[i-1][j-1];
}
}
}
return vv;
}
};
💖 电话号码的字母组合
给定一个仅包含数字 2 - 9 的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
可以用递归进行问题的求解,加入输入的数字组合是 “2 3 7”,根据数字对应的字母进行组合,类似于二叉树的深度遍历,大致思路图解如下:
代码如下:
class Solution {
//首先通过数字可以找到对应的字符串
string _numToStr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
//因为用到递归,所以这里还需要另外写一个递归子函数
//这里digits是数字串,如”237“,di 是数字串第几个字符,数字串走到第几个字符就说明递归到第几层
//combinestr是组合出来的字符串,strV用来保存组合字符串
void Combinations(const string& digits, size_t di, string combineStr, vector<string>& strV)
{
//当走完数字串,停止本次递归并返回
if(di == digits.size())
{
//组合出来的字符串在combineStr里面,将其存入strV中
strV.push_back(combineStr);
return;
}
//num是指当前走到数字串的第几个数字,对应组合字符串的第几层
int num = digits[di]-'0';
//取当前层的字符串,如第一层数字2对应的字符串就是“abc”
string str = _numToStr[num];
//进行排列组合
//依次取str里面的每个字母,和上一层传过来的字母进行组合,组合好以后传给下一层再继续组合
for(auto ch:str)
{
Combinations(digits,di+1,combineStr+ch,strV);
}
}
vector<string> letterCombinations(string digits) {
vector<string> strV;
if(digits.size() == 0)
{
return strV;
}
Combinations(digits,0,"",strV);
return strV;
}
};
class Solution {
string _numToStr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
void Combinations(const string& digits, size_t di, string combineStr, vector<string>& strV)
{
if(di == digits.size())
{
strV.push_back(combineStr);
return;
}
int num = digits[di]-'0';
string str = _numToStr[num];
for(auto ch:str)
{
Combinations(digits,di+1,combineStr+ch,strV);
}
}
递归展开图如下: