本文仅针对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。
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;
}
};
#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();
}
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;
}
};