Безкоштовно двійкове дерево


13

Отже, перш ніж прочитати деякі основні поняття з інформатики.

  1. Бінарне дерево - це динамічно розподілена структура (зазвичай використовується для впорядкованого зберігання).
  2. Через свою природу обхід бінарних дерев зазвичай рекурсивний;
    Це тому, що лінійне проходження (через цикл) не є природним, коли є два напрямки циклу.
    • Рекурсивний: це означає функцію, яка викликає себе.
  3. Старомодними мовами управління пам’яттю вимагає ручного управління пам’яттю.
    • Посібник: означає, що ви повинні зробити це самостійно.
  4. Під час керування пам'яттю вручну потрібно фактично попросити базову систему звільнити кожного члена дерева.
    • Безкоштовно: відновіть пам’ять у глобальний poos, щоб її можна було повторно використовувати, і у вас не вистачає пам'яті.
    • Звільнення: це робиться за допомогою виклику функції free()та передачі їй вказівника, який ви хочете відновити.
    • Вказівник: це як віртуальна палиця. В кінці - пам'ять. Коли ви запитуєте про пам'ять, вам надається вказівник (віртуальна палиця), що має пам'ять. Коли ви закінчите, ви повернете вказівник (віртуальна палиця).

Рекурсивне рішення:

freeTree(Node* node)
{
    freeTree(node->left);  
    freeTree(node->right);
    free(node);
}

Проблема тоді полягає в тому, що рекурсія означає, що ви неодноразово викликаєте одну і ту ж функцію. При цьому росте стека. Вирощування стека використовує більше пам’яті. Причина, коли ви звільняєте дерево, полягає в тому, що ви хочете повернути пам'ять, використовуючи більше пам'яті, є контрпродуктивним (навіть якщо ви повернете обидва біта пам'яті).

Нарешті питання:

Так проблема зосереджується на перетворенні рекурсивної версії вище в лінійне рішення (так що вам не доведеться використовувати пам'ять).

Дайте тип вузла

typedef struct Node Node;
struct Node
{
    Node* left;
    Node* right;
};

Напишіть функцію, щоб звільнити дерево цих вузлів.

Обмеження:

  • Неможливо використовувати рекурсію (навіть не побічно)
  • Неможливо виділити будь-який динамічний простір для відстеження.

  • Зверніть увагу, що існує рішення O (n)

Переможець:

  1. Найкраща складність.
  2. Перерва у краватці 1: Перший поданий
  3. Tie Break 2: Найменша кількість символів.

Відповіді:


7

Мені здається, дуже близький до O (n):

Це робить першу глибину прогулянки по дереву та використовує ->leftвказівник пройдених вузлів для відстеження батьків.

struct Node * node = root;
struct Node * up = NULL;

while (node != NULL) {
    if (node->left != NULL) {
        struct Node * left = node->left;
        node->left = up;
        up = node;
        node = left;
    } else if (node->right != NULL) {
        struct Node * right = node->right;
        node->left = up;
        node->right = NULL;
        up = node;
        node = right;
    } else {
        if (up == NULL) {
            free(node);
            node = NULL;
        }
        while (up != NULL) {
            free(node);
            if (up->right != NULL) {
                node = up->right;
                up->right = NULL;
                break;
            } else {
                node = up;
                up = up->left;
            }
        }
    }
}

+1 Додати галочку для єдиної відповіді. Це трохи складніше, ніж рішення, яке я подаю нижче, але дуже хороше.
Мартін Йорк

4

C99, 94, O (n)

Редагувати: начебто всі посилаються struct Nodeтак само Node, ніби typedefредактор, так і я.

це фактично мій перший гольф С. багато segfault.

у будь-якому випадку для цього потрібен C99, оскільки він використовує декларацію всередині a для першого оператора циклу.

void f(Node*n){for(Node*q;n;n=q)(q=n->left)?n->left=q->right,q->right=n:(q=n->right,free(n));}

навіть не користуючись #define!

цей алгоритм працює, трансформуючи дерево так, щоб верхній вузол не мав лівої дочірки, а потім видалив його та перейшов до правильного дочірнього.

наприклад, якщо ми почнемо з дерева

 1
/ \
2 3
 \
 4

алгоритм буде мутувати покажчики, так що дерево буде

2
 \
 1
/ \
4 3

тепер ми можемо легко видалити верхній вузол.


Я не використовував typedef, оскільки мій був у С ++ (ви забули ці невеликі відмінності між мовами). Я оновив питання, щоб воно працювало так само в C і C ++.
Мартін Йорк

@LokiAstari Я насправді не знаю c ++, і я нещодавно почав вивчати C. Але я знав достатньо, щоб відповісти на це :-)
гордий haskeller

1
Зараз я збираюся зробити +1. Але я ще не розробив, як це працює, тому я повернуся після індички. :-)
Мартін Йорк

@LokiAstari в основному використовує той факт, що C змішує вирази та заяви разом, щоб робити щось, використовуючи лише вирази
гордий haskeller

1

C / C ++ / Objective-C 126 символів (включає необхідний останній новий рядок)

#define b(t)(t->left||t->right)
void f(Node*r){while(r&&b(r)){Node**p=&r,*c=b(r);while(c)p=&c,c=b(c);free(*p);*p=0;}free(r);}

Не працює в O (n) час. Але ОП цього не вимагає, тому ось моє рішення O (n 2 ).

Алгоритм: Пройдіться до листа від кореня. Відпустіть його. Повторюйте, поки не залишиться листя. Відпустіть корінь.

Безголівки:

void freeTree (Node * root) {
    while (root && (root->left || root->right)) {
        Node ** prev = &root;
        Node * curr = root->left || root->right;
        while (curr != 0) {
            prev = &curr;
            curr = curr->left || curr->right;
        }
        free(*prev);
        *prev = 0;
    }
    free(root);
}

На жаль, це не вийде. ви не встановлюєте вказівник на лист NULL перед тим, як звільнити його. Таким чином, ви будете нескінченно продовжувати звільняти той самий вузол листя і ніколи не дістанетесь до того місця, коли ви звільните дерево.
Мартін Йорк

@LokiAstari: Дякуємо, що помітили помилку. Це має бути виправлено зараз (хоча я не перевіряв код).
Томас Едінг

1

c ++ 99 O (n)

Тут цикли відмінно підходять для створення ланцюга по списку, але не для ієрархій вгору та вниз. user300 керував цим (я вражений), але код важко читати.

Рішення полягає в перетворенні дерева в список.
Хитрість полягає в тому, щоб зробити це одночасно з видаленням вузлів.

void freeNode(Node* t)
{
    if (t == NULL)
    {   return;
    }

    // Points at the bottom left node.
    // Any right nodes are added to the bottom left as we go down
    // this progressively flattens the tree into a list as we go.    
    Node* bottomLeft    = findBottomLeft(t);


    while(t != NULL)
    {
        // Technically we don't need the if (it works fine without)
        // But it makes the code easier to reason about with it here.
        if (t->right != NULL)
        {
            bottomLeft->left = t->right;
            bottomLeft = findBottomLeft(bottomLeft);
        }
        // Now just free the curent node
        Node*   old = t;
        t = t->left;
        free(old);
    }
}

Node* findBottomLeft(Node* t)
{
    while(t->left != NULL)
    {
        t = t->left;
    }
    return t;
}

Версія для гольфу

void f(Node*t){Node*o,*l=t;for(;t;free(o)){for(;l->left;l=l->left);l->left=t->right;o=t;t=t->left;}}

Гольф розширений

void f(Node* t)
{
        Node*o,*l    = t;

        for(;t;free(o))
        {
            for(;l->left;l = l->left);
            l->left = t->right;
            o = t;
            t = t->left;
        }
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.