Как освободить всю память в дереве указателей

Я пишу постоянное дерево сегментов для следующей проблемы в 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) {};
};

У меня есть массив указателей с именем version, определенный следующим образом:

node *version[maxn];

В моем коде я хочу построить дерево сегментов с нуля, но я хочу освободить ранее выделенную память. Я не знаю, как это сделать. Я пытался сделать что-то вроде

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

Однако, когда я отправляю, я, кажется, не сильно уменьшил использование памяти. Раньше показывал около 1000мб, а теперь показывает 960мб. Я думаю, это потому, что я не освободил много памяти, потому что есть целое дерево указателей. Но я не знаю, как освободить их всех.

Это остальная часть моего кода, если вы хотите сослаться на него.

#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;
        }
    }
}

person SinByCos    schedule 23.01.2018    source источник
comment
Я думаю, было бы лучше, если бы вы не удаляли память до завершения работы программы, так как это снижает производительность. Вы можете сделать это, повторно используя узлы и/или используя массив с индексами, которые имитируют узлы-указатели.   -  person Cpp plus 1    schedule 23.01.2018
comment
поместите в деструктор узла удаление его левого и правого; как и вы... но прежде чем вы это сделаете, установите для левого->правого = null_ptr и правого->левого значение null_ptr (если левое и правое соответственно не равны нулю), иначе вы получите двойное удаление.   -  person UKMonkey    schedule 23.01.2018
comment
Как конкурентоспособному программисту, мне нужно использовать такие ярлыки, чтобы писать код быстро! Извините, если покажется плохим :(   -  person SinByCos    schedule 23.01.2018
comment
быстро писать код — ерунда!   -  person    schedule 23.01.2018
comment
Как конкурентоспособному программисту, мне нужно использовать такие ярлыки для быстрого написания кода! Почему? Есть ли ограничение по времени, как долго вы печатаете? Разве вы не можете редактировать и тестировать свой код в IDE и копировать/вставлять?   -  person drescherjm    schedule 23.01.2018
comment
Как вы можете знать или не знать, соревнования по программированию имеют ограничения по времени.   -  person SinByCos    schedule 23.01.2018
comment
Проблема требует использования массива, а не дерева,   -  person Maxim Egorushkin    schedule 23.01.2018
comment
Соревнования по программированию имеют ограничения по времени Я не думаю, что ваша скорость набора текста является частью ограничения.   -  person drescherjm    schedule 23.01.2018
comment
любая хорошая IDE автоматически сделает это за вас; вы просто потратите больше времени на написание #define, а затем попытаетесь не использовать переменную pb ни в одном из ваших классов.   -  person UKMonkey    schedule 23.01.2018
comment
Я каждый раз копирую и вставляю свой шаблон. Это обычная практика, лучшие конкурентоспособные программисты используют еще более надуманные шаблоны. В любом случае, этот спор бессмыслен. Если бы кто-нибудь мог ответить на мой вопрос, это было бы гораздо полезнее.   -  person SinByCos    schedule 23.01.2018
comment
@UKMonkey Я не уверен, что я имею в виду. Я думаю, что это требует рекурсивной функции для удаления всей памяти в дереве, но я получил SIGSEGV в последний раз, когда пробовал.   -  person SinByCos    schedule 23.01.2018
comment
верните удаление влево и вправо; создайте тест всего с двумя узлами, а затем используйте отладчик, чтобы посмотреть, точно что происходит. Я обещаю вам, что вы будете писать код быстрее, когда будете понимать каждую строку, чем если вы сэкономите себе всего несколько нажатий символов.   -  person UKMonkey    schedule 23.01.2018
comment
@SinByCos Вероятно, вы пытались удалить несуществующий узел или уже освобожденный узел. Взгляните на мой ответ. Не забудьте установить для пустых узлов значение nullptr, это избавит вас от некоторых ошибок. Кроме того, я думаю, что дерево может быть не лучшим вариантом здесь. И, наконец, часть о том, что конкурентоспособному программисту нужно использовать такие ярлыки для быстрого написания кода, — это история с драконами и магией. Удачи в отладке такого кода, когда вы получите SIGSEGV, как и вы.   -  person Mateusz Grzejek    schedule 23.01.2018
comment
Почему вы вообще тратите время на соревновательное программирование? Скорее всего, вы просто наберете кучу вредных привычек. Вместо этого присоединяйтесь к проекту с открытым исходным кодом и тратьте свое время на обучение, делая то, что действительно имеет ценность.   -  person Jesper Juhl    schedule 23.01.2018
comment
Лол, я учусь в старшей школе и стремлюсь к IOI. Я считаю, что лучше сначала развить фундаментальные алгоритмические навыки, обучение фактическому развитию может произойти в любое время позже. :)   -  person SinByCos    schedule 23.01.2018


Ответы (4)


Простой повтор:

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;
    }
}
person Mateusz Grzejek    schedule 23.01.2018
comment
Кажется, это должно работать, но я не знаю, почему мой код просто прерывается, когда я запускаю его на небольшом тестовом примере. :/ - person SinByCos; 23.01.2018
comment
Использовался следующий тестовый пример: 5 1 2 3 4 5 3 0 1 5 10 1 2 20 0 1 3 10 - person SinByCos; 23.01.2018
comment
Просто примечание: рекурсивные решения склонны к переполнению стека. Если вы не можете гарантировать, что размер дерева будет достаточно мал, чтобы не возникало этой проблемы, вам необходимо преобразовать его в итеративное решение. - person patatahooligan; 23.01.2018
comment
Это будет страдать от той же проблемы, что и у OP с их деструктором. releaseNode(n->left) ведет к releaseNode(left->right) ведет к... бесконечному циклу (хотя у OP были вложенные деструкторы, которые умерли довольно быстро) Вам нужно очистить правый и левый от левого и правого узлов соответственно - person UKMonkey; 23.01.2018
comment
Почему это должно привести к бесконечному циклу? Если в какой-то момент узел является nullptr, он возвращается, верно? - person SinByCos; 23.01.2018
comment
@SinByCos Дело не в бесконечном цикле. Такая рекурсия может легко привести к переполнению стека, если дерево большое. Здесь требуется итерационная версия. Извините, я не заметил, что вы уже пробовали это решение, см. обновленный ответ. - person Mateusz Grzejek; 23.01.2018
comment
До сих пор не уверен, почему, но мой код все еще прерывается. :/ Также это не приведет к переполнению стека, потому что размер дерева никогда не будет слишком большим, как вы можете видеть по ограничениям проблемы и тому, что пытается сделать мое постоянное дерево сегментов. - person SinByCos; 23.01.2018
comment
Может быть, это потому, что в моем обновлении я назначаю curr-›left = prev-›left, следовательно, когда я вызываю удаление, я пытаюсь удалить некоторые указатели, которые не были созданы с помощью нового оператора? - person SinByCos; 23.01.2018

Рекурсивное удаление необходимо для освобождения всей памяти (как это предлагается в предыдущем ответе), но для меня проблема в том, что вы пытаетесь удалить пространство памяти вектора, выделенного без использования «нового» оператора.

Это может вызвать некоторые проблемы, как описано в следующем сообщении: Это можно ли удалить не новый объект?

person LucaG    schedule 23.01.2018

В других ответах надлежащим образом описано, как настроить рекурсивную операцию delete, но, поскольку это C++, я чувствую себя обязанным добавить, что вся эта структура будет гораздо лучше обрабатываться с использованием умных указателей и стандарта. библиотечные контейнеры.

Например, если бы ваш массив был вместо std::vector<std::unique_ptr<node> >, а указатели узлов внутри класса также были бы unique_ptr, то все это было бы хорошо очищено, когда оно вышло бы за пределы области видимости.

Наконец, в своем вопросе вы заявляете, что хотите снова построить дерево с нуля. Это не представляется необходимым. Вероятно, вам было бы намного проще просто повторно использовать уже выделенную память для перестроенного дерева.

person Qwertycrackers    schedule 23.01.2018

Очевидно, вы расширяете свое дерево в функции update():

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

но я не вижу никакого обхода дерева, когда вы хотите удалить узлы.

Добавьте деструктор к узлу и сбросьте массив версий.

person Thinkeye    schedule 23.01.2018