数据结构-vector

本文介绍了C++中的数据结构vector,作为不定长数组的替代,它提供了灵活的大小调整。文章通过一个ACM-ICPC题目来说明vector在处理动态数组问题时的优势,解释了vector的基本操作和如何在代码中应用。此外,还简要提及了链表作为对比,并预告了关于迭代器的内容,但未展开详细讨论。

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示意图

也就是说它的空间是“连续”的。

我们可以用

    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:
写这篇的时候临近国庆,于是我咕了将近半个月,虽然打算国庆结束后就补完迭代器的部分,然而我很快就发烧在床上瘫了两天…就这么先扔上来吧(强颜欢笑)

…什么?还有链表?

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值