【问题标题】:How to free all the memory in a tree of pointers如何释放指针树中的所有内存
【发布时间】:2018-07-02 10:53:57
【问题描述】:

我正在为 Codechef 上的以下问题编写一个持久段树:https://www.codechef.com/problems/GIVEAWAY,我的节点结构如下所示:

struct node {
    int val;
    node *left, *right;
    node (int val, node *left, node *right) : val(val), left(left), right(right) {};
};

我有一个称为版本的指针数组,定义如下:

node *version[maxn];

在我的代码中,我想从头开始构建段树,但我想释放之前分配的内存。我不知道该怎么做。我试过做类似的事情

for (int i = 1; i <= N; i++) {
        delete version[i];
    }

但是,当我提交时,我的内存使用量似乎并没有减少很多。 之前它显示大约 1000mb,但现在显示 960mb。我认为这是因为我没有释放很多内存,因为有一整棵指针树。但我不确定如何释放所有这些。

这是我的代码的其余部分,如果您想参考它。

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
#include <stdio.h>
#include <queue>
#include <set>
#include <list>
#include <cmath>
#include <assert.h>
#include <bitset>
#include <cstring>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <iomanip> //cout << setprecision(node) << fixed << num
#include <stack>
#include <sstream>


#define all(x) x.begin(), x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define print(arr) for (auto it = arr.begin(); it != arr.end(); ++it) cout << *it << " "; cout << endl;
#define debug(x) cout << x << endl;
#define debug2(x,y) cout << x << " " << y << endl;
#define debug3(x,y,z) cout << x << " " << y << " " << z << endl;

typedef long long int ll;
typedef long double ld;
typedef unsigned long long int ull;
typedef std::pair <int, int> ii;
typedef std::vector <int> vi;
typedef std::vector <ll> vll;
typedef std::vector <ld> vld;

const int INF = int(1e9);
const ll INF64 = ll(1e18);
const ld EPS = 1e-9, PI = 3.1415926535897932384626433832795;
using namespace std;
const int maxn = (int)5e5+10;
int A[maxn], N;

struct node {
    int val;
    node *left, *right;
    node (int val, node *left, node *right) : val(val), left(left), right(right) {};
    //~node () { delete left; delete right; }
};

#define null new node (0, NULL, NULL);

node *version[maxn];

void update(node *prev, node *curr, int L, int R, int idx, int val) {
    if (L == R) {
        assert(idx == L);
        curr->val = val;
    }
    else {
        int mid = (L+R)/2;
        if (idx <= mid) {
            curr->right = prev->right;
            curr->left = null;
            update(prev->left, curr->left, L, mid, idx, val);
        }
        else {
            curr->left = prev->left;
            curr->right = null;
            update(prev->right, curr->right, mid+1, R, idx, val);
        }
        curr->val = curr->right->val + curr->left->val;
    }
}

int query(node *curr, int L, int R, int li, int ri) {
    if (ri < L || li > R)
        return 0;
    if (li <= L && ri >= R)
        return curr->val;
    int mid = (L+R)/2;
    return query(curr->left, L, mid, li, ri) + query(curr->right, mid+1, R, li, ri);
}

map <int, int> occ;

void build () {
    //cout << "building..\n";
    vector <ii> V;
    for (int i = 1; i <= N; i++) {
        V.pb(mp(A[i], i));
    }
    sort(all(V));
    occ.clear();
    for (int i = 1; i <= N; i++) {
        delete version[i];
    }
    for (int i = 1; i <= N; i++) {
        ii e = V[i-1];
        occ[e.fi] = i;
        version[i] = null;
        update(version[i-1], version[i], 1, N, e.se, 1);
    }
}

int main() {
    scanf("%d", &N);
    for (int i = 1; i <= N; i++) {
        scanf("%d", &A[i]);
    }
    version[0] = null;
    version[0]->right = version[0];
    version[0]->left = version[0];
    int Q;
    scanf("%d", &Q);
    int block = (int)sqrt(Q);
    for (int i = 0; i < Q; i += block) {
        build();
        vector <ii> updates;
        for (int j = i; j < i+block && j < Q; j++) {
            int type;
            scanf("%d", &type);
            if (type == 0) {
                int a, b, c;
                scanf("%d %d %d", &a, &b, &c);
                auto it = occ.lower_bound(c);
                int cnt = 0;
                if (it != occ.begin()) {
                    it = prev(it);
                    cnt = query(version[it->second], 1, N, a, b);
                }
                int ans = b-a+1-cnt;
                for (ii update : updates) {
                    int idx = update.fi;
                    int pre = A[idx];
                    int nw = update.se;
                    if (a <= idx && idx <= b) {
                        if (nw >= c && pre < c)
                            ans++;
                        if (nw < c && pre >= c)
                            ans--;
                    }
                }
                printf("%d\n", ans);
            }
            else {
                int a, b;
                scanf("%d %d", &a, &b);
                updates.pb(mp(a, b));
            }
        }
        for (ii update : updates) {
            A[update.fi] = update.se;
        }
    }
}

非常感谢您的帮助!

编辑:

最好的解决方案是创建一个没有指针的持久段树。我可以轻松地重新创建树,而不必递归地删除所有内存,这很容易实现并且很烦人,特别是因为我不熟悉指针。这大大减少了内存使用量。

新节点看起来像:

struct node {
    int val;
    int left, right;
    node() : val(0), left(0), right(0) {}
    node(int val) : val(val), left(0), right(0) {}
    node(int val, int l, int r) : val(val), left(l), right(r) {}
};

如果有人感兴趣,这里是实现。

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
#include <stdio.h>
#include <queue>
#include <set>
#include <list>
#include <cmath>
#include <assert.h>
#include <bitset>
#include <cstring>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <iomanip> //cout << setprecision(node) << fixed << num
#include <stack>
#include <sstream>


#define all(x) x.begin(), x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define print(arr) for (auto it = arr.begin(); it != arr.end(); ++it) cout << *it << " "; cout << endl;
#define debug(x) cout << x << endl;
#define debug2(x,y) cout << x << " " << y << endl;
#define debug3(x,y,z) cout << x << " " << y << " " << z << endl;

typedef long long int ll;
typedef long double ld;
typedef unsigned long long int ull;
typedef std::pair <int, int> ii;
typedef std::vector <int> vi;
typedef std::vector <ll> vll;
typedef std::vector <ld> vld;

const int INF = int(1e9);
const ll INF64 = ll(1e18);
const ld EPS = 1e-9, PI = 3.1415926535897932384626433832795;
using namespace std;

const int maxn = (int)5e5+10;
int A[maxn], upd[maxn], N;


struct node {
    int val;
    int left, right;
    node() : val(0), left(0), right(0) {}
    node(int val) : val(val), left(0), right(0) {}
    node(int val, int l, int r) : val(val), left(l), right(r) {}
};

node stree[35*maxn];
int root[maxn], nodeCnt = 0;

void update(int old, int &curr, int L, int R, int idx, int val) {
    curr = ++nodeCnt;
    stree[curr] = stree[old];
    if (L == R) {
        assert(idx == L);
        stree[curr].val += val;
    }
    else {
        int mid = (L+R)/2;
        if (idx <= mid) {
            update(stree[old].left, stree[curr].left, L, mid, idx, val);
        }
        else {
            update(stree[old].right, stree[curr].right, mid+1, R, idx, val);
        }
        stree[curr].val = stree[stree[curr].left].val + stree[stree[curr].right].val;
    }
}

int query (int curr, int L, int R, int li, int ri) {
    if (curr == 0 || ri < L || li > R)
        return 0;
    if (li <= L && ri >= R)
        return stree[curr].val;
    int mid = (L+R)/2;
    return query(stree[curr].left, L, mid, li, ri) + query(stree[curr].right, mid+1, R, li, ri);
}

map <int, int> occ;

void build () {
    //cout << "building..\n";
    vector <ii> V;
    for (int i = 1; i <= N; i++) {
        V.pb(mp(A[i], i));
    }
    sort(all(V));
    occ.clear();
    memset(root, 0, sizeof(root));
    for (int i = 0; i <= nodeCnt; i++) {
        stree[i] = node();
    }
    nodeCnt = 0;
    for (int i = 1; i <= N; i++) {
        ii e = V[i-1];
        occ[e.fi] = i;
        update(root[i-1], root[i], 1, N, e.se, 1);
    }
}

int main() {
    scanf("%d", &N);
    for (int i = 1; i <= N; i++) {
        scanf("%d", &A[i]);
    }
    int Q;
    scanf("%d", &Q);
    int block = (int)sqrt(Q);
    for (int i = 0; i < Q; i += block) {
        build();
        vector <pair <int, ii>> updates;
        memset(upd, 0, sizeof(upd));
        for (int j = i; j < i+block && j < Q; j++) {
            int type;
            scanf("%d", &type);
            if (type == 0) {
                int a, b, c;
                scanf("%d %d %d", &a, &b, &c);
                auto it = occ.lower_bound(c);
                int cnt = 0;
                if (it != occ.begin()) {
                    it = prev(it);
                    cnt = query(root[it->second], 1, N, a, b);
                }
                int ans = b-a+1-cnt;
                for (pair <int, ii> update : updates) {
                    int idx = update.fi;
                    int pre = A[idx];
                    int nw = update.se.fi;
                    if (upd[idx] != update.se.se) continue;
                    if (a <= idx && idx <= b) {
                        if (nw >= c && pre < c)
                            ans++;
                        if (nw < c && pre >= c)
                            ans--;
                    }
                }
                printf("%d\n", ans);
            }
            else {
                int a, b;
                scanf("%d %d", &a, &b);
                updates.pb(mp(a, mp(b, j)));
                upd[a] = j;
            }
        }
        for (pair <int, ii> update : updates) {
            A[update.fi] = update.se.fi;
        }
    }
}

【问题讨论】:

  • 我认为在程序终止之前不删除内存会更好,因为这会降低性能。您可以通过重用节点和/或使用具有模仿指针节点的索引的数组来做到这一点。
  • 放入节点析构函数删除其左右;正如你所做的那样......但在你这样做之前将 left->right = null_ptr & right->left 设置为 null_ptr (如果 left 和 right 分别不为空)否则你最终会被双重删除。
  • 快速编写代码废话!
  • 作为一名有竞争力的程序员,我需要使用这样的快捷方式来快速编写代码! 为什么?打字有时间限制吗?您不能在 IDE 中编辑和测试您的代码并复制/粘贴吗?
  • 竞争性编程比赛确实有时间限制我不认为你的打字速度是限制的一部分。

标签: c++ pointers struct segment-tree


【解决方案1】:

其他答案已经适当地涵盖了如何设置递归 delete 操作,但由于这是 C++,我觉得有必要补充一点,使用 智能指针 可以更好地处理整个结构,并且标准库容器

举个例子,如果你的数组是std::vector&lt;std::unique_ptr&lt;node&gt; &gt;,并且类中的节点指针也是unique_ptr,那么当它超出范围时,整个东西就会被很好地清理掉。

最后,在您的问题中,您声明您想从头开始重新构建树。这似乎没有必要。只需将已分配的内存重新用于重建树,您可能会轻松得多。

【讨论】:

    【解决方案2】:

    简单复现:

    void releaseNode(node* n)
    {
      if (!n) return;
      releaseNode(n->left);
      releaseNode(n->right);
      delete n;
    }
    

    还有循环本身的更新:

    for (int i = 1; i <= N; i++)
    {
      releaseNode(version[i]);
    }
    

    如果数组version 的大小始终是编译时常量,您可以将其包装成一个简单的函数:

    template <size_t N>
    void releaseArrayOfNodes(node*(&array)[N])
    {
      for (int i = 1; i <= N; i++)
      {
        releaseNode(array[i]);
      }
    }
    

    然后写:

    releaseArrayOfNodes(version);
    

    抱歉,我没有注意到这样一个事实,递归解决方案在这里可能不是一个选项,今天对我来说很糟糕,不知道为什么。

    您可以尝试迭代解决方案:

    void releaseNode(node* n)
    {
        std::stack<node*> context;
        context.push(n);
    
        while (!context.empty())
        {
            node* top = context.top();
            context.pop();
    
            if (top->left != nullptr)
                context.push(top->left);
    
            if (top->right != nullptr)
                context.push(top->right);
    
            delete top;
        }
    }
    

    【讨论】:

    • 这似乎应该可以工作,但我不知道为什么我的代码在一个小测试用例上运行时会中止。 ://
    • 使用的测试用例是这样的:5 1 2 3 4 5 3 0 1 5 10 1 2 20 0 1 3 10
    • 请注意,递归解决方案容易发生堆栈溢出。如果不能保证树的大小足够小不会出现这个问题,则需要将其转换为迭代解决方案。
    • 这将遇到与 OP 的析构函数相同的问题。 releaseNode(n-&gt;left) 导致 releaseNode(left-&gt;right) 导致...一个无限循环(尽管 OP 有嵌套的析构函数很快就死了)您需要分别空白左右节点的右侧和左侧
    • 为什么会导致无限循环?如果在任何时候该节点是它返回的 nullptr,对吧?
    【解决方案3】:

    递归删除是确保释放所有内存所必需的(如上一个答案中的建议),但对我来说,问题是您试图删除分配的向量的内存空间而不使用“new”运算符。

    这可能会导致一些问题,如以下帖子中所述:Is it possible to delete a non-new object?

    【讨论】:

      【解决方案4】:

      您显然在 update() 函数中扩展了您的树:

      curr->left = null;
      curr->left = null;
      

      但是当你想删除节点时,我没有看到树的任何遍历。

      向节点添加析构函数并重置版本数组。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-09-27
        • 1970-01-01
        • 2020-06-23
        • 2011-09-27
        • 1970-01-01
        • 2014-03-28
        • 1970-01-01
        • 2014-03-13
        相关资源
        最近更新 更多