Освобождение динамически выделенных узлов графа в C

Я хочу построить граф, который создает новый родительский узел путем слияния двух дочерних узлов. Код ниже должен объединить узлы a и b в родительский узел c. Затем узлы a и c для создания родительского узла d:

    a   b
    |---|
      |
  a   c
  |---|
    |
    d

Когда я пытаюсь освободить граф, начинающийся с узла d, я получаю ошибку сегментации, и я не знаю, почему. Каким-то образом это работает, если я не использую один и тот же узел дважды в графе. Однако я хочу иметь возможность использовать один и тот же узел более одного раза. Может кто-нибудь, пожалуйста, скажите мне, что мне здесь не хватает?

#include <stdlib.h>


struct Node {

    int data;

    struct Node *child1;
    struct Node *child2;

};

struct Node *NewNode(double data) {

    struct Node *node = NULL;
    node = malloc(sizeof(*node));

    if (node == NULL) {
        return node;
    }

    node->data = data;
    node->child1 = NULL;
    node->child2 = NULL;

    return node;
}

struct Node* merge(struct Node *self, struct Node *other) {

    struct Node *node = NewNode(-1);
    node->child1 = self;
    node->child2 = other;

    return node;
}


void free_graph(struct Node **node) {
    if (*node != NULL) {
        free_graph(&(*node)->child1);
        free_graph(&(*node)->child2);
        free(*node);
        *node = NULL;
    }
}

int main(void){

    struct Node *a = NewNode(1);
    struct Node *b = NewNode(2);
    struct Node *c = merge(a, b);
    struct Node *d = merge(a, c);
    free_graph(&d);

}

Вы действительно хотите использовать один и тот же узел дважды? Или вы хотите использовать одно и то же значение дважды?

klutt 07.01.2023 13:24

@klutt Я хочу использовать один и тот же узел дважды. Однако не совсем уверен, что вы имеете в виду под стоимостью. Из узла d и c мне нужен прямой доступ к узлу a.

Gilfoyle 07.01.2023 13:32

Ну, тогда то, что вы хотите, это не дерево :)

klutt 07.01.2023 13:34

Хорошо, тогда, наверное, это график? Я обновил вопрос.

Gilfoyle 07.01.2023 13:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы дважды помещаете a в намеченное дерево, поэтому free_graph пытается освободить его дважды. Вызов free дважды по одному и тому же адресу из одного и того же исходного распределения является неправильным.

Если вы хотите иметь настоящее дерево, не помещайте в него ни один узел дважды. Если вы хотите иметь структуру данных, которая может иметь один и тот же узел дважды, либо используйте отдельные копии узла (например, два разных выделения для struct Node с одинаковым значением для data), либо предусмотрите структуру данных, чтобы избежать освобождения его дважды (например, добавьте счетчик ссылок к struct node, чтобы подсчитать, сколько раз он находится в дереве в данный момент, и освободите узел только тогда, когда его счетчик ссылок достигнет нуля).

Я хотел бы иметь структуру данных без создания копии, так как это усложнило бы дальнейшую работу. Не совсем уверен, как счетчик ссылок работает в C. Я пытался установить указатель на узел NULL, чтобы он не освобождался более одного раза. Почему это не работает в моем случае?

Gilfoyle 07.01.2023 13:42

@Gilfoyle: Re «Я пытался установить указатель на узел NULL, чтобы он не освобождался более одного раза. Почему это не работает в моем случае?»: когда a как дочерний элемент d освобождается, free_graph рекурсивно вызывает себя, чтобы освободить своих дочерних элементов, одним из которых является c, а один из дочерних элементов c указывает на a. В этом рекурсивном вызове память для a освобождается. Когда рекурсивные вызовы завершены, free_graph все еще находится в процессе освобождения a и вызывает free(*node) для a, но память для a уже освобождена при рекурсивном вызове.

Eric Postpischil 07.01.2023 13:48

Это не работает, потому что ваше «дерево» не соответствует вашей иллюстрации и на самом деле технически не является деревом. То, что у вас есть, выглядит так:

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

Чтобы освободить все на таком графике, я бы предложил иметь отдельный связанный список, чтобы отслеживать все, что вам нужно освободить.

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

Поиск списка всех узлов в ориентированном графе

Я предполагаю, что вы могли бы сделать что-то вроде этого псевдо:

getAllNodes(root, nodes)
    if root // NULL check
        if not node in nodes // If it's the first time we visit the node
            // Add this node to the list of visited nodes
            nodes = nodes + [root]

            // And then call this function recursively on the children
            getAllNodes(root->left, nodes)
            getAlLNodes(root->right, nodes)

nodes = []
getAllNodes(root, nodes)
for node in nodes
    free(node)

У деревьев есть приятная особенность: они никогда не содержат петель. Но ориентированные графы это делают, поэтому вам нужно проверить, не посещался ли уже узел. Обратите внимание, что для того, чтобы это работало, его нужно вызывать из корня. Или, если быть более точным, каждый узел должен быть доступен из узла. Но это не так уж отличается от дерева.

Я думаю, вы могли бы каким-то образом переместить свободную часть внутрь, чтобы создать функцию freeAllNodes(), но это более гибко. Возможно, вам нужен список для других целей. Поэтому мое предложение в таком случае — просто сделать freeAllNodes() звонок getAllNodes().

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

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

Gilfoyle 07.01.2023 13:35

Другие вопросы по теме