0

I'm trying to build a max heap in VC++ using Visual Studio 2008 v9.0.30729.1 SP.

In the tree, each node looks like:

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

A single node creation logic goes like this:

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

I have managed to create and insert elements in the tree (insertion logic working fine). I'm stuck with the logic of removing a node (a leaf, to be precise) from this tree.

I've tried four different approaches for the same:

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;

}

(Uncomment APPROACH 1/2/3/4 for the desired effect).

None of this seems to work. I need to assign a zero/null value to the left/right pointer of the previous node.

How to make this work? Please help.

metsburg
  • 2,021
  • 1
  • 20
  • 32
  • 1
    Why are you using `malloc` instead of `new` in a C++ program? Also, please show `deleteLeaf`. Also, in general *draw the operations on paper first*. If you're trying to figure out how to delete a node by writing code, don't. First draw the actions on paper to see what needs to be done, *then* apply what you did on paper to code. – PaulMcKenzie Sep 04 '14 at 05:13
  • deleteLeaf is given here, it's a recursion. The operation is drawn on paper and so far things were going fine. I've done this before in C using, as far as I remember, GCC compiler. Assigning null to a node worked fine there. I just want to figure out how to get this working here. – metsburg Sep 04 '14 at 05:19
  • 1
    Well, the reason I was fooled by what you posted is that nowhere do you actually deallocate the memory using `free`. So your implementation has a memory leak. Also, you're still using 'C', as there is no actual C++ usage in the code you posted. – PaulMcKenzie Sep 04 '14 at 05:22
  • Ok, agreed, I'm still using C. I'll edit the tag. I tried freeing the node... see approach 4. But the question still remains, how do I get rid of the memory block. free(node) is not working. – metsburg Sep 04 '14 at 05:24
  • 1
    What is deleteLeaf supposed to do? If it's supposed to delete pointers to the given leaf in parent nodes, that obviously can not work. Also, what is the returned value? Without that, it's impossible to even think about fixing this function. Or, in other words, this function perfectly conforms to its (nonexistant) documentation, so it doesn't need fixing. – Ulrich Eckhardt Sep 04 '14 at 05:26
  • deleteLeaf is supposed to delete the last element of the tree at the last level. The value returned should be the data contained in the deleted leaf. – metsburg Sep 04 '14 at 05:42
  • "the last element of the tree at the last level" -- There's no such thing. Do you even understand what a (binary, in this case) tree *is*? Perhaps what you mean is that you want to delete the rightmost leaf and return its data ... then your function is misnamed and the return type is wrong. – Jim Balter Sep 04 '14 at 05:49
  • I believe I do. Let's check wiki: http://en.wikipedia.org/wiki/Binary_heap#Delete – metsburg Sep 04 '14 at 05:51
  • You do understand that that the code block I posted is a part of the procedure and not the entire operation itself. Of course this won't work on its own. I'm merely saying this is the part with which I had been stuck. I'm not citing the above code sample as an example of a max/min heap. – metsburg Sep 04 '14 at 05:53
  • 1
    What I understand is that you didn't bother to say in your question that you are trying to delete the rightmost leaf of the tree, and that the name and signature you have given for your function doesn't look anything like that. See my answer for how to do that. The link you gave is for deleting the *root* of a heap, and getting the rightmost node is part of that ... you should have given that link and a clear description of what you wanted to do *in the question*, not as an afterthought in the comments. – Jim Balter Sep 04 '14 at 06:24

3 Answers3

2

Instead of writing 4 random lines of code and calling them "approaches", try actually specifying a function that does something meaningful. A function that takes a heap as an argument should be called DeleteHeap, not DeleteLeaf. Since it deletes a heap, there's nothing for it to return. So, how do you delete a heap? Well, if the heap is a leaf (it has no left or right subtree), delete that, else delete the subtrees by calling DeleteHeap recursively. Code that and you're done.

Edit: You left a comment:

deleteLeaf is supposed to delete the last element of the tree at the last level. The value returned should be the data contained in the deleted leaf.

Well, that's news. We aren't mind-readers. Your question didn't say this, and the function name and signature are wrong for this, too.

Let's start with the name -- DeleteRightmostLeaf. And the return type ... data_t*. Even the argument type is wrong ... it should be heap_t**, because we have to store a NULL into the pointer.

So, DeleteRightmostLeaf takes a pointer to a pointer to a heap. If that heap is a leaf node, store NULL in the pointer to it, extract its data pointer, free the node (in that order ... otherwise you're accessing deleted memory, which isn't allowed), and return the data pointer.

If the heap isn't a leaf node, then call DeleteRightmostLeaf recursively on the pointer to its rightmost subtree -- the right subtree if that's not NULL, else the left subtree. Voila, you're done.

Note that, in both cases, it's very easy to come up with the answer if one just thinks clearly about what they need to do.

As a bonus, here's an iterative solution. I haven't tested or even compiled this.

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;
}
Jim Balter
  • 16,163
  • 3
  • 43
  • 66
  • Hmmm... like I said, I admit the question needed a better construct. And your uncompiled suggestion definitely looks more elegant. I'll try this out. – metsburg Sep 04 '14 at 06:21
  • @metsburg Unfortunately, this doesn't satisfy your requirement either, because the rightmost node isn't the one you want. *None* of the answers on this page are right ... you can't find the last node on the last level with simple recursion ... it requires a breadth-first search to find the *deepest level*. There's a really good reason that heaps are normally represented with arrays, not trees. But you can find the last node of the last level if you take advantage of the *heap requirement* that the data is always greater (or always less) than the data of the subnodes. – Jim Balter Sep 04 '14 at 06:35
  • The objective here is to remove the max node and to rearrange the tree to retain its max-heap property. Finding the last element becomes easier with array. But, let us say, in the tree based implementation, the program fails to find the last node on the last level. Instead, it ends up with some other node. But, there'll always be a heapify operation after the last node (or some other erroneous node) has been swapped with the root of the heap. If the root node is bubbled down properly, I believe the max-heap property would not be violated. – metsburg Sep 04 '14 at 06:46
  • I don't want to start an unrelated question here in the comments. But this is what I've been planning. – metsburg Sep 04 '14 at 06:47
  • @metsburg Next time, ask the question you actually want answered, and put vital info like "max heap" in the title ... "deleting node from tree" is uninformative and led everyone astray. – Jim Balter Sep 04 '14 at 06:48
2

To delete a node in a tree you need to

  1. free the memory and do the cleanup for the node
  2. fix the pointer you used to reach the node, making it NULL

the part 2 can be solved in two ways:

A) the parent does the fixing B) the deletion routine receives the address of the address of the node (extra level of indirection).

For solution A the code is simply

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

but the caller needs to fix the pointer used to reach the node. For example like

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);

The solution B looks like:

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

and for example code deleting a leaf of the tree is

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);
6502
  • 112,025
  • 15
  • 165
  • 265
  • I gave a similar solution. Unfortunately, both are wrong because what is needed is the last node on the last level of the tree ... which cannot be found without searching the entire tree. However, this is a max heap, which has structural rules that allow the node to be found. – Jim Balter Sep 04 '14 at 06:46
1

Try this modification of your method:

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

One question: which value should be returned by this function? (now it always returns 0). It is hard to understand what you are trying to do (we haven't description of the function, examples of expected results and so on). So, I suspect, that code above is not solution. But it might be first step in understanding of the problem.

Ilya
  • 4,583
  • 4
  • 26
  • 51
  • 1. It should return the data of the node being deleted. I understand it will always return zero now, no problem. I'll change the logic later. Please understand that this is work in progress and not a bug in an existing project. Just deleting the node will be enough for now. – metsburg Sep 04 '14 at 05:31
  • Ok, since you need to return deleted data, it is better to changed type of returned value (`data_t*` instead of `node_t*`). But it is not typical approach. I recommend you to copy data using another method (`getData()`) to simplify logic of your code. – Ilya Sep 04 '14 at 05:35
  • This part: ` if (heap->left!=0) deleteLeaf(heap->left); if (heap->right!=0) deleteLeaf(heap->right);` seems wrong – it will go down the tree with `heap->left` pointer and after getting back from recursion go again along the `heap->right` path... – CiaPan Sep 04 '14 at 05:40
  • "It should return the data of the node being deleted." -- No, it should not. You're recursively deleting a heap so you can't possibly return all its data ... getting at all its data is what the heap is *for*. You only want to delete it when you're *done* with the data. – Jim Balter Sep 04 '14 at 05:43
  • 1
    @CiaPan, as I understand, it was the goal: delete both sub-trees. Why do you think it is wrong? – Ilya Sep 04 '14 at 05:44
  • 2
    @CiaPan No, it's not wrong (and if it were, millions of other instances of tree recursion would also be wrong). "go again along the heap->right path" -- of course it will go along the right path as it should, but there's no "again" about it; left and right are two, not one. – Jim Balter Sep 04 '14 at 05:45
  • @Ilya: The first edit you suggested, if((heap->left==0) && (heap->right==0)) { free(heap->data); free(heap); heap = 0; } works fine in deleting the last element at the last level. Which is what should happen with a delete max/min in a heap (after the topmost element has been removed). Sorry if my method naming had been misleading. – metsburg Sep 04 '14 at 05:49
  • Unfortunately, my answer (and the algorithm was given by 6502) doesn't actually find the last node of the last level. For that, it's essential to know that this is a max heap and to take advantage of that. – Jim Balter Sep 04 '14 at 06:52
  • @Ilya Your code seems to delete a subtree, starting with the given node, but the function name suggests rather deleting a single leaf node. And this is what OP wrote soon after your answer: _'deleteLeaf is supposed to delete the last element of the tree at the last level. The value returned should be the data contained in the deleted leaf.'_ Despite his total confusion about what he wants to achieve, your code certainly would not do what he needs. – CiaPan Oct 15 '14 at 16:31
  • @CiaPan, thank you for this explanation! It was hard to understand what OP needs at the beginning. Now it is easier, because we have many additional comments. – Ilya Oct 16 '14 at 06:52