vector是不定长数组,也就是说它的长度是不固定的,简单地说就是“按需分配”。这听上去似乎有点麻烦,但在声明数组时如果我们并不清楚数组的长度,并且简单粗暴地使用#define MAXN 1000000
会导致内存失去了梦想——就算不,仍有大量的内存成了咸鱼。这个时候,我们就需要vector数组。
先看一道例题:
从左到右有n个木块,编号为0~n-1,要求模拟以下四种操作:
- move a onto b:把a和b上方的木块全部归位,然后把a摞在b上面。
- move a over b:把a上方的木块全部归位,然后把a放在b所在木块堆的顶部。
- pile a onto b:把b上方的木块全部归位,然后把a及上面的木块整体摞在b上面。
- pile a over b:把a及上面的木块整体摞在b所在木块堆的顶部。
遇到quit时终止一组数据。a和b在同一堆的指令时非法指令,应当忽略。
所有操作结束后,输出每个位置的木块列表,按照从底部到顶部的顺序排列。
(出自UVa 101,小紫书p110)
由于每个木块堆的最大高度并没有限制,如果开一个a[n][n]的数组,显然浪费了很多空间,并且出题人很可能会故意卡MLE,于是我们需要一个“按需分配”数组。
链表
在介绍vector前,先提一下链表。
在c语言中,我们并没有c++丰富的STL,也就是说需要人工实现不定长数组:
struct Node
{
int num; //用于储存数据
Node* next;
};
这段代码声明了一个结构体Node,它包含了一个int型变量num和一个指针next,让我们有请灵魂画师来帮助我们理解。
我们的链表是由若干个这样的“小块”组成的,由于创建的“小块”之间没有任何联系,所以我们需要用一个指针next将它们连接到一起,也就是用next指向下一个“小块”,这样我们就能将它们“链”在一起。
每一个next都指向下一个块的地址,我们只需要访问当前块的next储存的地址,就可以到达下一个块。
由于我们只用在需要的时候申请一个新的块,并把它和之前的链表连在一起,所以可以达到不定长数组的作用。
但显然,构造一个链表十分麻烦,并且鄙人已经基本忘了怎么写链表,所以我们需要一个C艹。(c语言的链表构建我会在之后补上(flag++))
vector
在C++中,我们可以用vector来代替链表,这需要
#include <vector>
在vector中,我们(暂时)不需要再思考指针和它的小伙伴们,可以把vector看作一个正常的数组:
也就是说它的空间是“连续”的。
我们可以用
vector <int> v;
声明一个vector数组,它的元素为int型。当然,你也可以换成double型或者string型甚至是结构体。
vector为我们提供了以下操作:
v.push_back(n); //将元素n加入到v的尾部
v.pop_back(); //将v的末端的元素删除
v.empty(); //判断数组是否为空,若为空则返回真,否则返回假
v.size(); //返回v中元素的数量
v.resize(n); //将v的长度设为n
如果我们想访问v中的元素,只需要像数组一样访问下标即可:
ans=v[i];
num=v[i]+v[i+1];
这时候,我们可以解决前面的例题了。
代码示例
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const int MAXN=10000;
vector <int> v[MAXN];
int n;
int find(int a,int& p,int& h) //寻找木块a(或b)所在位置和高度
{
p=v[a][0];
for(h=1;h<v[p].size();h++)
{
if(v[p][h]==a) return 0;
}
}
void clear(int p,int h) //将位置p高度h的木块上方的所有木块归位
{
for(int i=h+1;i<v[p].size();i++)
{
int j=v[p][i];
v[j][0]=j;
}
v[p].resize(h+1);
}
void move(int p1,h1,p2) //将位置p1高度h1的木块及上方的所有木块移到位置p2的顶端
{
for(int i=h1;i<v[p1].size();i++)
{
int j=v[p1][i];
v[p2].push_back(j);
v[j][0]=p2;
}
v[p1].resize(h1);
}
int main()
{
int a,b;
string s1,s2;
while(cin>>n>>s1)
{
for(int i=0;i<n;i++) //初始化v
{
v[i].clear();
v[i].push_back(i); //v[i][0]保存木块i所在的位置
}
while(s1!="quit")
{
cin>>a>>s2>>b;
int p1,h1,p2,h2;
find(a,p1,h1);
find(b,p2,h2);
if(p1!=p2)
{
if(s2=="onto") clear(p2,h2);
if(s1=="move") clear(p1,h1);
move(p1,h1,p2);
}
cin>>s1;
}
for(int i=0;i<n;i++) //一组数据结束,打印木块列表
{
cout<<i<<":";
for(int j=1;j<v[i].size();j++)
cout<<v[i][j]<<" ";
cout<<endl;
}
}
return 0;
}
鄙人用v[i][0]来保存木块i所在的位置(初始为i),这样在每次find的时候可以减少一次for循环查找木块i的位置,避免最坏情况下find为O(n²)而导致超时。
迭代器
还记得之前说的“我们(暂时)不需要再思考指针和它的小伙伴们”么?
是的,我们只是暂时不需要,但事实上还有个叫做“迭代器”的设定。
由于不(wo)可(jiu)抗(shi)力(lan)的因素,此部分之后再更新……
PS:
写这篇的时候临近国庆,于是我咕了将近半个月,虽然打算国庆结束后就补完迭代器的部分,然而我很快就发烧在床上瘫了两天…就这么先扔上来吧(强颜欢笑)
…什么?还有链表?