C++刷题时应该返回对象还是返回对象的指针?

本文仅针对C++写算法题时,遇到要返回对象的情况时,应该返回对象还是返回对象的指针做出探讨。

众所周知,C++的变量和java、python不同,C++的变量并不是天然的指针,所以对象类型的变量之间的赋值远比java、python耗时,例如下列代码:

//cpp
vector a(10000);
vector b = a;    // 拷贝构造,a和b两个vector有各自的独立空间,需要消耗时间和空间

//python
a = [0 for _ in range(10000)]
b = a    # 地址赋值,把对象a的地址赋给了b,a和b指向同一个list,几乎不需要消耗时间和空间

在使用C++写算法题时,偶尔会遇到函数中返回对象的场景,那么是直接返回对象比较好,还是应该返回一个对象的指针呢?相信不少人担心直接返回对象会发生临时的构造,从而导致消耗大量时间,造成最后TLE。于是我进行了一些实验:

要解决的题目是Leetcode 2003. 每棵子树内缺失的最小基因值,使用暴力合并的解法(无法AC,仅为了实验),在leetcode和codeforces上构造测试用例进行测试(看看在极端环境下的表现)。其中leetcode平台的测试我并没有使用playground(因为在playground上跑代码并不能显示内存消耗),使用的是在779. 第K个语法符号题内提交代码的方式进行测试(因为本题本身想要AC所需的时间和空间几乎为0,不会对测试造成太大的时间和空间干扰)。在codeforces上直接使用custom test进行测试,很方便。除此以外,还使用可AC的启发式合并解法测试了下实际解题时的开销。

结果(跑三次取平均)

leetcode测试 (n=3210) codeforces测试 (n=3210) codeforces测试 (n=10000) leetcode实际解题
返回对象(dfs1) 1877ms / 384MB 390 ms / 5476 KB 3961 ms / 6932 KB 1111ms / 397MB
返回对象指针(dfs2) 1159ms / 339MB 327 ms / 115880 KB MLE 928ms / 416MB
返回对象指针并释放内存(dfs3) 1915ms / 384MB 390 ms / 5436 KB 3883 ms / 6992 KB 1217ms / 436MB

结果还是很意外的,看来C++的RVO(返回值优化)确实还不错,整体来看,返回对象(dfs1)和返回对象指针并释放内存(dfs3)在两个平台上效率都差不多。但是leetcode平台不知道编译器做了什么神奇的优化,不用手动释放内存也不会MLE,而且时间消耗很明显比正常的两种写法要好。看来以后leetcode上写题如果TLE了,感觉时间复杂度没法再优化的情况下,可以尝试下返回对象指针而且不释放内存的写法。

除此以外,也能看出codeforces的判题机效率是真的好,差不多的代码和数据,居然比leetcode上跑要快这么多,难怪说codeforces把很多人养出了stl依赖症hhh。

leetcode测试代码(提交至779. 第K个语法符号)

bool flag;
class S {
public:
    vector<int> res;
    vector<int> g[100005];
    unordered_set<int> dfs1(int now, vector<int>& nums)
    {
        unordered_set<int> se;
        se.insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs1(v, nums);
            // if (temp.size() > se.size()) swap(temp, se);
            for (auto e:temp) se.insert(e);
            res[now] = max(res[now], res[v]);
        }
        while (se.count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs2(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs2(v, nums);
            // if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            // delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs3(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs3(v, nums);
            // if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    vector<int> smallestMissingValueSubtree() {
        vector<int> parents(3210), nums(3210); //如果是4000直接TLE,没法得到结果
        //构造测试数据
        iota(parents.begin(), parents.end(), -1);
        iota(nums.begin(), nums.end(), 1);

        res.resize(parents.size(), 1);
        for (int i = 0; i < parents.size(); i++)
            if (parents[i] != -1)
                g[parents[i]].push_back(i);
        
        dfs1(0, nums);
        // dfs2(0, nums);
        // dfs3(0, nums);
        return {};
    }
};
class Solution {
public:
    int kthGrammar(int n, int k) {   
        if (!flag) //leetcode会多次调用函数,弄个全局变量保证只执行一次,不然TLE
        {
            S().smallestMissingValueSubtree();
            flag = true;
        }
        return __builtin_popcount(k-1)%2;
    }
};

codeforces代码(使用custom test,GNU G++17 7.3.0)

#include 
using namespace std;

class S {
public:
    vector<int> res;
    vector<int> g[100005];
    unordered_set<int> dfs1(int now, vector<int>& nums)
    {
        unordered_set<int> se;
        se.insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs1(v, nums);
            // if (temp.size() > se.size()) swap(temp, se);
            for (auto e:temp) se.insert(e);
            res[now] = max(res[now], res[v]);
        }
        while (se.count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs2(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs2(v, nums);
            // if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            // delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs3(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs3(v, nums);
            // if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    vector<int> smallestMissingValueSubtree() {
        vector<int> parents(10000), nums(10000); 
        //构造测试数据
        iota(parents.begin(), parents.end(), -1);
        iota(nums.begin(), nums.end(), 1);

        res.resize(parents.size(), 1);
        for (int i = 0; i < parents.size(); i++)
            if (parents[i] != -1)
                g[parents[i]].push_back(i);
        
        dfs1(0, nums);
        // dfs2(0, nums);
        // dfs3(0, nums);
        return {};
    }
};

int main()
{
	S().smallestMissingValueSubtree();
}

leetcode实际解题代码(提交至Leetcode 2003. 每棵子树内缺失的最小基因值)

class Solution {
public:
    vector<int> res;
    vector<int> g[100005];
    unordered_set<int> dfs1(int now, vector<int>& nums)
    {
        unordered_set<int> se;
        se.insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs1(v, nums);
            if (temp.size() > se.size()) swap(temp, se);
            for (auto e:temp) se.insert(e);
            res[now] = max(res[now], res[v]);
        }
        while (se.count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs2(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs2(v, nums);
            if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            // delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    unordered_set<int>* dfs3(int now, vector<int>& nums)
    {
        unordered_set<int>* se = new unordered_set<int>();
        se->insert(nums[now]);
        for (auto v : g[now])
        {
            auto temp = dfs3(v, nums);
            if (temp->size() > se->size()) swap(temp, se);
            for (auto e:(*temp)) se->insert(e);
            delete temp;
            res[now] = max(res[now], res[v]);
        }
        while (se->count(res[now])) ++res[now];
        return se;
    }
    vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
        res.resize(parents.size(), 1);
        for (int i = 0; i < parents.size(); i++)
            if (parents[i] != -1)
                g[parents[i]].push_back(i);
        
        dfs1(0, nums);
        // dfs2(0, nums);
        // dfs3(0, nums);
        return res;
    }
};

你可能感兴趣的:(数据结构与算法,c++,算法,leetcode)