学习笔记V——由 woj 1478 Key Logger 想到的list效率问题

昨天校赛一塌糊涂。我和我的队友都已经进入更年期了。^_^

比赛中这是个简单题,一看到题目我就想到可以写,而且很简单。开始想到的是用STL的list很简单的去写,但list每次删除和添加元素之后迭代器指在什么地方不太清楚,于是就手写链表。结果TLE了,想到TLE只有可能是分配内存和释放内存的问题,于是改用数组模拟了链表,通过。今天看到大家说起list的效率,也看到哑熊(Dumbear)用list过了这个题,于是就默写了昨天比赛的代码,去进行了测试,结果发现我的程序在时间和内存上都略有优势。代码如下:

我的代码:

/*
 * Author: stormdpzh
 * Created Time:  2013/4/22 19:27:49
 * File Name: woj_1478.cpp
 */
#include <iostream>
#include <cstdio>
#include <sstream>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#include <algorithm>
#include <functional>

#define sz(v) ((int)(v).size())
#define rep(i, n) for(int i = 0; i < n; i++)
#define repf(i, a, b) for(int i = a; i <= b; i++)
#define repd(i, a, b) for(int i = a; i >= b; i--)
#define out(n) printf("%d\n", n)
#define mset(a, b) memset(a, b, sizeof(a)

using namespace std;

typedef long long lint;
const int INF = 1 << 30;
const int MaxN = 1000015;

char s[MaxN];
int pre[MaxN];
int next[MaxN];

void gao()
{
    int len = strlen(s + 1);
    repf(i, 0, len) {
        pre[i] = next[i] = -1;
    }
    int id = 0;
    repf(i, 1, len) {
        if(s[i] == '-') {
            if(id != 0) {
                int t1 = pre[id], t2 = next[id];
                next[t1] = t2;
                if(t2 != -1) pre[t2] = t1;
                id = t1;
            }
        }
        else if(s[i] == '<') {
            if(pre[id] != -1) id = pre[id];
        }
        else if(s[i] == '>') {
            if(next[id] != -1) id = next[id];
        }
        else {
            int t = next[id];
            next[id] = i;
            pre[i] = id;
            next[i] = t;
            if(t != -1) pre[t] = i;
            id = i;
        }
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    repf(cas, 1, t) {
        scanf("%s", s + 1);
        gao();
        int id = next[0];
        printf("Case %d: ", cas);
        while(id != -1) {
            printf("%c", s[id]);
            id = next[id];
        }
        puts("");
    }
    return 0;
}
哑熊(Dumbear)的代码:

#include <cstdio>
#include <string>
#include <list>

using namespace std;

int t;

list<char> text;
list<char>::iterator cursor;

void process(char c) {
    if (c == '-') {
        if (cursor != text.begin()) {
            --cursor;
            cursor = text.erase(cursor);
        }
    } else if (c == '<') {
        if (cursor != text.begin())
            --cursor;
    } else if (c == '>') {
        if (cursor != text.end())
            ++cursor;
    } else {
        cursor = text.insert(cursor, c);
        ++cursor;
    }
}

void solve() {
    text.clear();
    cursor = text.begin();
    char c;
    while ((c = getchar()) != '\n')
        process(c);
    string s;
    for (list<char>::iterator i = text.begin(); i != text.end(); ++i)
        s += *i;
    printf("Case %d: %s\n", ++t, s.c_str());
}

int main() {
    int t;
    scanf("%d", &t);
    while (getchar() != '\n');
    for (int i = 0; i < t; ++i)
        solve();
    return 0;
}
这不禁引起了我的思考:list在时间上和空间上的不足在哪里?

首先,我的程序在时间上是完全O(1)的,每个操作都是O(1)的完成,而list肯定会涉及到内存的分配回收,这肯定会有时间的浪费。但有一点:list肯定不会像手写的链表那样每次删除元素就立马释放内存,不然也可能出现像昨天那样的TLE的问题。

其次,list的空间也比数组占用多。注意到我是有三个数组的,所以list应该其实是多用了不少内存的。但由于list不要求内存的连续性,所以它肯定不会像vector那样出现在申请内存时的浪费(vector为了提高时间效率,在连续的内存空间不够用时,会重新开辟出当前两倍的空间,具体参见 http://blog.csdn.net/stormdpzh/article/details/8727509),这个浪费更可能是由于它像vector那样,类似的,在删除元素时没有立马释放内存。这样就解释了为什么它比手写的链表快很多。

另外,本人进行了一个测试,发现vector和list相比,如果不断的push_back,即只在最后加入元素的话,在元素较少时,vector比list快,而在元素增多时,list的速度会越来越有优势。这个其实也是可以理解的:vector在元素较少时,不会像list那样插入一个元素都要重新申请空间,而他们的插入又都不会有元素的移动,所以vector占优;而在元素增多时,vector一旦出现连续内存不足的情况,就得重新申请空间并移动所有元素,这势必会降低它的效率。

ps:以上很多内容有很大推测成分,欢迎拍砖。

总结:STL在做ACM比赛的题目时,恰当的应用肯定会有很大的好处,但是这种应用可能也会导致基本功的不扎实,比如sort用多了连快排都不能马上写出来了。所以,在用STL的同时要尽量深入理解,本人在前面的面试中也深刻感受到了这个问题。

again:进入更年期的男人。

你可能感兴趣的:(学习笔记V——由 woj 1478 Key Logger 想到的list效率问题)