Застрял при удалении узла из дерева

Я пытаюсь создать максимальную кучу в VC ++, используя Visual Studio 2008 v9.0.30729.1 SP.

В дереве каждый узел выглядит так:

typedef struct node{
    struct data_t *data;
    struct node_t *left;
    struct node_t *right;
}node_t;

Логика создания одного узла выглядит следующим образом:

node_t* createNode(int id, int pID, float probability)
{
    node_t *temp = (node_t *)malloc(sizeof(node_t));
    data_t *data = (data_t *)malloc(sizeof(data_t));

    data->id = id;
    data->pID = pID;
    data->probability = probability;

    temp->data = data;
    temp->left = 0;
    temp->right = 0;

    return temp;
}

Мне удалось создать и вставить элементы в дерево (логика вставки работает нормально). Я застрял в логике удаления узла (если быть точным, листа) из этого дерева.

Я пробовал четыре разных подхода к одному и тому же:

node_t* deleteLeaf(node_t* heap)
{
    node_t* leaf;


    if((heap->left==0) && (heap->right==0))
    {
        //heap = 0;     //APROACH 1
        //heap->data = 0;   //APROACH 2

        return heap;
    }
    else if((heap->left!=0) && (heap->right==0))
    {
        leaf = deleteLeaf(heap->left);

    }
    else 
    {
        leaf = deleteLeaf(heap->right);

    }

    //leaf = 0;         //APROACH 3
    //free(leaf);           //APROACH 4 

    return leaf;

}

(Раскомментируйте ПОДХОД 1/2/3/4 для желаемого эффекта).

Кажется, ничего из этого не работает. Мне нужно присвоить нулевое / нулевое значение левому / правому указателю предыдущего узла.

Как заставить это работать? Пожалуйста помоги.


person metsburg    schedule 04.09.2014    source источник
comment
Почему вы используете malloc вместо new в программе на C ++? Также покажите deleteLeaf. Кроме того, как правило, сначала нарисуйте операции на бумаге. Если вы пытаетесь выяснить, как удалить узел, написав код, не делайте этого. Сначала нарисуйте действия на бумаге, чтобы увидеть, что нужно сделать, затем примените то, что вы сделали на бумаге, к коду.   -  person PaulMcKenzie    schedule 04.09.2014
comment
Здесь указан deleteLeaf, это рекурсия. Операция нарисована на бумаге, и пока все идет хорошо. Я делал это раньше на C, используя, насколько я помню, компилятор GCC. Назначение null узлу там отлично работало. Я просто хочу выяснить, как заставить это работать здесь.   -  person metsburg    schedule 04.09.2014
comment
Что ж, причина, по которой я был обманут тем, что вы опубликовали, заключается в том, что вы фактически нигде не освобождаете память с помощью free. Итак, в вашей реализации есть утечка памяти. Кроме того, вы все еще используете «C», поскольку в опубликованном вами коде нет фактического использования C ++.   -  person PaulMcKenzie    schedule 04.09.2014
comment
Хорошо, согласен, я все еще использую C. Я отредактирую тег. Я пробовал освободить узел ... см. Подход 4. Но остается вопрос, как избавиться от блока памяти. free (узел) не работает.   -  person metsburg    schedule 04.09.2014
comment
Что должен делать deleteLeaf? Если предполагается удалить указатели на данный лист в родительских узлах, это явно не сработает. Кроме того, каково возвращаемое значение? Без этого невозможно даже думать о том, чтобы исправить эту функцию. Или, другими словами, эта функция полностью соответствует своей (несуществующей) документации, поэтому не требует исправления.   -  person Ulrich Eckhardt    schedule 04.09.2014
comment
deleteLeaf должен удалить последний элемент дерева на последнем уровне. Возвращаемое значение должно быть данными, содержащимися в удаленном листе.   -  person metsburg    schedule 04.09.2014
comment
последний элемент дерева на последнем уровне - такого нет. Вы хоть понимаете, что такое (в данном случае двоичное) дерево ? Возможно, вы имеете в виду, что хотите удалить крайний правый лист и вернуть его данные ... тогда ваша функция неверно названа и тип возврата неверен.   -  person Jim Balter    schedule 04.09.2014
comment
Я верю, что знаю. Давайте проверим вики: en.wikipedia.org/wiki/Binary_heap#Delete   -  person metsburg    schedule 04.09.2014
comment
Вы понимаете, что этот блок кода, который я опубликовал, является частью процедуры, а не самой операцией. Конечно, само по себе это не сработает. Я просто говорю, что это та часть, с которой я застрял. Я не цитирую приведенный выше образец кода в качестве примера максимальной / минимальной кучи.   -  person metsburg    schedule 04.09.2014
comment
Насколько я понимаю, вы не удосужились сказать в своем вопросе, что вы пытаетесь удалить крайний правый лист дерева, и что имя и подпись, которые вы указали для своей функции, совсем не выглядят. См. Мой ответ, как это сделать. Ссылка, которую вы дали, предназначена для удаления корня кучи, и получение самого правого узла является частью этого ... вы должны были предоставить эту ссылку и четкое описание того, что вы хотели сделать в вопросе, а не в комментариях.   -  person Jim Balter    schedule 04.09.2014


Ответы (3)


Чтобы удалить узел в дереве, вам необходимо:

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

Часть 2 можно решить двумя способами:

A) родитель выполняет исправление. B) процедура удаления получает адрес адреса узла (дополнительный уровень косвенного обращения).

Для решения A код просто

void deleteNodeA(Node *p) {
    if (p) {
        // Here we don't really need part 2 because
        // we're going to destroy the whole node containing
        // the pointers anyway.
        deleteNodeA(p->left);  // add p->left = NULL if you like
        deleteNodeA(p->right); // add p->right = NULL if you like
        free(p->data);
        free(p);
    }
}

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

Node *p = root, *parent = NULL;
while (p && (p->left || p->right)) {
    // Not a leaf... go left if possible o right otherwise
    parent = p;
    p = p->left ? p->left : p->right;
}

// 2: Fix the pointer in parent
if (parent) {
    if (p == parent->left) {
        parent->left = NULL;
    } else {
        parent->right = NULL;
    }
} else {
    // No parent... this was the root of the tree
    root = NULL;
}

deleteNodeA(p);

Решение B выглядит так:

void deleteNodeB(Node **p) { // Note the double pointer
    if (*p) {
        deleteNode(&((*p)->left));  // Note the &
        deleteNode(&((*p)->right)); // Note the &
        free((*p)->data);
        free(*p);
        *p = NULL; // (2): fixing the pointer
    }
}

и, например, код, удаляющий лист дерева,

Node **p = &root;
while ((*p) && ((*p)->left || (*p)->right)) {
    // Not a leaf... go left if possible o right otherwise
    p = ((*p)->left) ? &((*p)->left) : &((*p)->right));
}
deleteNodeB(p);
person 6502    schedule 04.09.2014
comment
Я дал аналогичное решение. К сожалению, оба ошибаются, потому что нужен последний узел на последнем уровне дерева ... который не может быть найден без поиска всего дерева. Однако это максимальная куча, в которой есть структурные правила, позволяющие найти узел. - person Jim Balter; 04.09.2014

Вместо того, чтобы писать 4 случайные строки кода и называть их «подходами», попробуйте на самом деле указать функцию, которая делает что-то значимое. Функция, которая принимает кучу в качестве аргумента, должна называться DeleteHeap, а не DeleteLeaf. Поскольку он удаляет кучу, ему нечего возвращать. Итак, как удалить кучу? Что ж, если куча является листом (у нее нет левого или правого поддерева), удалите это, иначе удалите поддеревья, рекурсивно вызывая DeleteHeap. Кодируйте это, и все готово.

Изменить: вы оставили комментарий:

deleteLeaf должен удалить последний элемент дерева на последнем уровне. Возвращаемое значение должно быть данными, содержащимися в удаленном листе.

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

Начнем с названия - DeleteRightmostLeaf. И тип возврата ... data_t*. Даже тип аргумента неверен ... он должен быть heap_t**, потому что мы должны сохранить NULL в указателе.

Итак, DeleteRightmostLeaf принимает указатель на указатель на кучу. Если эта куча является листовым узлом, сохраните NULL в указателе на нее, извлеките указатель данных, освободите узел (в этом порядке ... в противном случае вы получите доступ к удаленной памяти, что недопустимо) и верните данные указатель.

Если куча не является листовым узлом, то вызовите DeleteRightmostLeaf рекурсивно по указателю на его крайнее правое поддерево - правое поддерево, если оно не NULL, иначе левое поддерево. Вуаля, готово.

Обратите внимание, что в обоих случаях очень легко придумать ответ, если просто четко подумать о том, что им нужно делать.

В качестве бонуса вот итеративное решение. Я не тестировал и даже не компилировал это.

data_t* DeleteRightmostLeaf(node_t** pheap)
{
    node_t* pnode = *pheap;

    if (!pnode)
        return NULL; // empty heap

    while (pnode->left || pnode->right)
    {
        pheap = pnode->right ? &pnode->right : &pnode->left;
        pnode = *pheap;
    }

    *pheap = NULL;
    data_t* pdata = pnode->data;
    free(pnode);
    return pdata;
}
person Jim Balter    schedule 04.09.2014
comment
Хммм ... как я уже сказал, я признаю, что вопрос требовал лучшей конструкции. И ваше некомпилированное предложение определенно выглядит элегантнее. Я попробую это сделать. - person metsburg; 04.09.2014
comment
@metsburg К сожалению, это тоже не удовлетворяет вашим требованиям, потому что крайний правый узел не тот, который вам нужен. Ни один из ответов на этой странице не является правильным ... вы не можете найти последний узел на последнем уровне с помощью простой рекурсии ... требуется поиск в ширину, чтобы найти самый глубокий уровень. Есть действительно веская причина, по которой кучи обычно представлены массивами, а не деревьями. Но вы можете найти последний узел последнего уровня, если воспользуетесь преимуществом требования к куче, что данные всегда больше (или всегда меньше), чем данные подузлов. - person Jim Balter; 04.09.2014
comment
Цель здесь - удалить максимальный узел и переставить дерево, чтобы сохранить его свойство max-heap. Поиск последнего элемента становится проще с массивом. Но, скажем, в реализации на основе дерева программе не удается найти последний узел на последнем уровне. Вместо этого он попадает в какой-то другой узел. Но всегда будет операция heapify после того, как последний узел (или какой-либо другой ошибочный узел) был заменен на корень кучи. Я полагаю, что если корневой узел будет очищен должным образом, свойство max-heap не будет нарушено. - person metsburg; 04.09.2014
comment
Я не хочу начинать здесь, в комментариях, не связанный с этим вопрос. Но это то, что я планировал. - person metsburg; 04.09.2014
comment
@metsburg В следующий раз задайте вопрос, на который вы действительно хотите получить ответ, и поместите в заголовок важную информацию, такую ​​как максимальная куча ... удаление узла из дерева неинформативно и сбивает всех с пути. - person Jim Balter; 04.09.2014

Попробуйте эту модификацию вашего метода:

node_t* deleteLeaf(node_t* heap)
{
  if (!heap)
    return 0;

  if (heap->left!=0)
    deleteLeaf(heap->left);
  if (heap->right!=0)
    deleteLeaf(heap->right);

  if (heap->data)
    free(heap->data); // free data
  free(heap); // free leaf
  heap = 0;
  return heap;
}

Один вопрос: какое значение должна возвращать эта функция? (теперь он всегда возвращает 0). Трудно понять, что вы пытаетесь сделать (у нас нет описания функции, примеров ожидаемых результатов и т. Д.). Итак, я подозреваю, что приведенный выше код не является решением. Но это может быть первым шагом к пониманию проблемы.

person Ilya    schedule 04.09.2014
comment
1. Он должен вернуть данные удаляемого узла. Я понимаю, что теперь он всегда будет возвращать ноль, без проблем. Позже изменю логику. Пожалуйста, поймите, что это работа, а не ошибка в существующем проекте. Пока достаточно просто удалить узел. - person metsburg; 04.09.2014
comment
Хорошо, поскольку вам нужно вернуть удаленные данные, лучше изменить тип возвращаемого значения (data_t* вместо node_t*). Но это нетипичный подход. Я рекомендую вам копировать данные другим методом (getData()), чтобы упростить логику вашего кода. - person Ilya; 04.09.2014
comment
Эта часть: `if (heap-› left! = 0) deleteLeaf (heap- ›left); if (heap- ›right! = 0) deleteLeaf (heap-› right); `кажется неправильным - он пойдет вниз по дереву с указателем heap->left и после выхода из рекурсии снова пройдёт по пути heap->right ... - person CiaPan; 04.09.2014
comment
Он должен вернуть данные удаляемого узла. - Нет, не должно. Вы рекурсивно удаляете кучу, поэтому вы не можете вернуть все ее данные ... получение всех ее данных - вот что такое куча для. Вы хотите удалить его только тогда, когда вы закончите работу с данными. - person Jim Balter; 04.09.2014
comment
@CiaPan, насколько я понимаю, это была цель: удалить оба поддерева. Как вы думаете, почему это неправильно? - person Ilya; 04.09.2014
comment
@CiaPan Нет, это не так (и если бы это было так, миллионы других экземпляров рекурсии дерева также были бы неправильными). еще раз пройти по куче- ›правильный путь - конечно, он пойдет по правильному пути, как и должен, но опять же об этом нет; слева и справа два, а не один. - person Jim Balter; 04.09.2014
comment
@Ilya: Первое предложенное вами изменение if ((heap- ›left == 0) && (heap-› right == 0)) {free (heap- ›data); бесплатно (куча); куча = 0; } отлично работает при удалении последнего элемента на последнем уровне. Вот что должно произойти с удалением max / min в куче (после удаления самого верхнего элемента). Извините, если мое название метода ввело в заблуждение. - person metsburg; 04.09.2014
comment
К сожалению, мой ответ (а алгоритм дал 6502) на самом деле не находит последний узел последнего уровня. Для этого важно знать, что это максимальная куча, и пользоваться этим. - person Jim Balter; 04.09.2014
comment
@Ilya Ваш код, кажется, удаляет поддерево, начиная с данного узла, но имя функции предполагает удаление одного листового узла. И вот что OP написал вскоре после вашего ответа: 'deleteLeaf должен удалить последний элемент дерева на последнем уровне. Возвращаемое значение должно быть данными, содержащимися в удаленном листе. ' Несмотря на его полное замешательство в том, чего он хочет достичь, ваш код определенно не будет делать то, что ему нужно. - person CiaPan; 15.10.2014
comment
@CiaPan, спасибо за это объяснение! Поначалу было сложно понять, что нужно OP. Теперь это проще, потому что у нас много дополнительных комментариев. - person Ilya; 16.10.2014