Remove node in a singly linked list - list

Suppose you have a pointer p to a node in a singly linked list that is
not on the last node in the list. You have no other pointer to the
list except the following links in each node.
Describe an O(1) algorithm that logically removes the value stored in
the node pointed to by p (suggestion: use the next node).
The idea to do it is to transfer information from the next node to the current node pointed to by p and the next node is removed from the list. My question is why don't we remove the node pointed by our pointer instead of removing the next node. I am a bit confused.

You have a pointer to the node, but no way to change the previous node's reference to it; therefore you must copy and remove the next node.
Your pointer p only references the current node. Since you can't modify prevous->next to point at the node with value 3, you must copy next node into the current one, both value and next pointer.

My question is why don't we remove the node pointed by our pointer instead of removing the next node.
You can't remove the current node.
The question states:
You have no other pointer to the list except the following links in each node.
This means that there are nodes prior to yours and, in particular, there is an immediately prior node that points to yours. You cannot delete your node because you have no way of changing the immediately prior node.
You can't remove the next node.
The question states that you need to remove:
the value stored in the node pointed to by p.
That's definitely not the value of the next node.

Summary
You will replace the node at the address of p with the next node in the list. When you start you have:
+----+ +----+
| | | |
-->| p |-->|next|-->
| | | |
+----+ +----+
0x1234
After you replace the node at the current address you will have:
+----+
| |
-->|next|-->
| |
+----+
0x1234
Nothing prior to the address of p in the list changes. When you are done, you simply:
delete p;
Detail
Your question setup tells you you have a pointer p to a node that is not the last node in your list and you need to remove the value at that node. Great, most of the work is already done, no need to iterate to find the node to remove. So how to make use of the current pointer to remove the value (actually replace the node at that address with the next node)?
Here you can make use of the address of the pointer p (&p) and the pointer to the next node (p->next) to copy the content of the next node to the present address, overwriting the current node contents with it. Since you haven't changed where the original pointer p points, it still points to the memory for the original node that was linked at the current address allowing you to free the memory using pointer p to complete the operation.
So what you want to do is first, replace the node at the current address in the list, e.g.
node **ppn = &p; /* a pointer to pointer holding current address */
*ppn = p->next; /* replace node at address of p with p->next */
(where p points to the current node, &p is the address for the pointer that is linked in your list, and p->next is a separate pointer to the next node in the list)
Since the previous next pointer still points to this address, and you have replaced the node at the current address with the next node in the list (whose next pointer still points to the correct following node), this is all that is required to remove the node that was original linked at p in the list.
To complete the process and avoid a memory leak, you need to free (delete) the memory for the node you removed. Since you have not changed where p itself points, it still points to the node you have removed from your list. So you can simply:
delete p;
and you are done.
It will take a minute to wrap your head around how you have used the address of p &p as a placeholder in your list, and you have simply replaced the node that was originally at that address in your list, with the next node in your list. This removes the node pointed to by p from the list. prev->next still points to the address of p and the new node you have assigned to that address still has a valid ->next pointer to the node that follows it in the list, so your list is complete sans one node. The pointer p still points to the memory holding the node that was removed, so you simply delete p; to tidy up.
That is what is explained in Linus on Understanding Pointers, though I never really loved that explanation as I found it a bit thin and awkwardly written.
Now you can take this knowledge and look at the del_node() and delnode() functions I pointed you to and begin digesting how it all works. It may take an hour or a day for the light-bulb to wink on and it all fall into place. That's fine. Thinking about how you can use both the address of a pointer as well as where the pointer points (e.g. the address it holds as its value) takes a bit of wrestling with to make peace with.
Full Example
A full example using:
*ppn = p->next; /* replace node at address with next */
free (p); /* delete current */
to remove the 4th node in a list of 1 - 10 using only p and its address:
#include <stdio.h>
#include <stdlib.h>
typedef struct node_t {
int data;
struct node_t *next;
} node_t;
/** add node at end of list, update tail to end */
node_t *add (node_t **head, int v)
{
node_t **ppn = head, /* pointer to pointer to head */
*pn = *head, /* pointer to head */
*node = malloc (sizeof *node); /* allocate new node */
if (!node) { /* validate allocation */
perror ("malloc-node");
return NULL;
}
node->data = v; /* initialize members values */
node->next = NULL;
while (pn) {
ppn = &pn->next;
pn = pn->next;
}
return *ppn = node; /* add & return new node */
}
/** print all nodes in list */
void prn (node_t *l)
{
if (!l) {
puts ("list-empty");
return;
}
for (node_t *n = l; n; n = n->next)
printf (" %d", n->data);
putchar ('\n');
}
/** delete all nodes in list */
void del_list (node_t *l)
{
node_t *n = l;
while (n) {
node_t *victim = n;
n = n->next;
free (victim);
}
}
int main (void) {
node_t *list = NULL, *p = NULL, **ppn = NULL;
int node_to_rm = 4;
for (int i = 0; i < 10; i++)
if (!add (&list, i + 1))
return 1;
prn (list);
/* iterate to find 4th node (with data = 5) */
for (ppn=&list, p=list; p && node_to_rm; ppn=&p->next, p=p->next)
node_to_rm--;
*ppn = p->next; /* replace node at address with next */
free (p); /* delete current */
prn (list);
del_list (list);
}
Example Use/Output
$ ./bin/ll_rm_single_node_given_p
1 2 3 4 5 6 7 8 9 10
1 2 3 4 6 7 8 9 10
Memory Use/Error Check
$ valgrind ./bin/ll_rm_single_node_given_p
==22684== Memcheck, a memory error detector
==22684== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22684== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22684== Command: ./bin/ll_rm_single_node_given_p
==22684==
1 2 3 4 5 6 7 8 9 10
1 2 3 4 6 7 8 9 10
==22684==
==22684== HEAP SUMMARY:
==22684== in use at exit: 0 bytes in 0 blocks
==22684== total heap usage: 11 allocs, 11 frees, 1,184 bytes allocated
==22684==
==22684== All heap blocks were freed -- no leaks are possible
==22684==
==22684== For counts of detected and suppressed errors, rerun with: -v
==22684== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Related

Dynamic memory Allocation in Linked list insert function

I was going through a tutorial on Linked Lists in C++. I found the following code for the implementation for inserting an element in the Linked List:
/* Given a reference (pointer to pointer) to the head
of a list and an int, appends a new node at the end */
void append(Node** head_ref, int new_data)
{
/* 1. allocate node */
Node* new_node = new Node();
Node *last = *head_ref; /* used in step 5*/
/* 2. put in the data */
new_node->data = new_data;
/* 3. This new node is going to be
the last node, so make next of
it as NULL*/
new_node->next = NULL;
/* 4. If the Linked List is empty,
then make the new node as head */
if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}
/* 5. Else traverse till the last node */
while (last->next != NULL)
last = last->next;
/* 6. Change the next of last node */
last->next = new_node;
return;
}
In step 1, we are declaring a pointer with name new_node and it is pointing to a dynamically created block.
What I couldn't understand is that, if the function is called 4 times then how can a new pointer variable with the same name be created on each call? Since we are using dynamic memory allocation, therefore it won't be dumped when we return from function.
So, how is this piece of code working?
Variable names don't exist at runtime, only at compile-time.
The new_node variable represents a chunk of memory that is local to the append() function. Each time append() is called, a new memory chunk is created when it enters scope, and that chunk is released when it goes out of scope.
Each call to new allocates a new block of dynamic memory. In this case, the memory address of that block is being stored inside that local memory chunk that new_node represents.
append()
+----------------+
| new_node |
| +------------+ | +-------------+
| | 0xABCDABCD |-|------| Node object |
| +------------+ | +-------------+
+----------------+

Reason of child node assignments after deletion from Binary Search Tree?

I have a point which i haven't understood while learning BST Deletion mechanism. Could you explain me why there is an assignment (p->rchild =, p->lchild =) each time the Delete(Node* p, int key) is called? Actually, I thought that the Delete(Node* p, int key) method just keeps returning without any mutation so the tree doesn't change.
And while i was looking for an explanation, i stumbled into this sentence :
We have to make assignments after deletion else we will end up having
duplicate nodes.
If you agree with this statement, could you please explain it to me?
Node* BST::Delete(Node *p, int key) {
Node* q;
if (p == nullptr){
return nullptr;
}
if (p->lchild == nullptr && p->rchild == nullptr){
if (p == root){
root = nullptr;
}
delete p;
return nullptr;
}
if (key < p->data){
p->lchild = Delete(p->lchild, key);
} else if (key > p->data){
p->rchild = Delete(p->rchild, key);
} else {
if (Height(p->lchild) > Height(p->rchild)){
q = InPre(p->lchild);
p->data = q->data;
p->lchild = Delete(p->lchild, q->data);
} else {
q = InSucc(p->rchild);
p->data = q->data;
p->rchild = Delete(p->rchild, q->data);
}
}
return p;
}
why there is an assignment (p->rchild =, p->lchild =) each time the Delete(Node* p, int key) is called?
If the data is found, then the goal is to have a tree that has one node less. The algorithm will uses a value-swapping mechanism to ensure that the node that will actually be removed, is always a leaf node. The deletion of a leaf node consists of two actions:
The removal from memory;
The update of its parent so that the child pointer that references this deleted node is set to a null pointer.
As the algorithm recurses right to that leaf node, it cannot set the null pointer to its parent, as there is at this stage no reference available to the parent node. For that the caller should do something, since the caller does have a reference to the parent.
So when the recursive traversal arrives at the leaf node, it needs to convey to the caller that this node should be detached. It does so by returning a null pointer, and the agreement is that the caller (whose current node p is the parent) should assign the returned pointer to relevant child pointer. That way the deleted node is really detached from the rest of the tree.
Actually, I thought that the Delete(Node* p, int key) method just keeps returning without any mutation so the tree doesn't change.
Surely the tree must change somehow to have a node deleted from it. The change happens in the assignment to p->lchild or p->rchild
I stumbled into this sentence :
We have to make assignments after deletion else we will end up having duplicate nodes.
This is true. Let's take an example tree:
7
/ \
3 8
/ \ \
1 5 9
/ / \
0 4 6
Now let's see what happens if we call Delete(root, 3). p points to the node with value 7. We go to the left with a recursive call:
p->lchild = Delete(p->lchild, key);
In the recursive execution context, we get a new p which points to the node with value 3. This is the value we're looking for, so we get into the outer else block. As the heights of the subtrees below that node are equal, we get into the inner else block. There we assign:
q = InSucc(p->rchild);
This q will reference the node with value 4. And now a duplication happens. We copy the data from q to p. That comes down to deleting the value 3 from the tree:
p->data = q->data;
But now we have twice the value 4 in the tree.
7
/ \
4* 8
/ \ \
1 5 9
/ / \
0 4* 6
So the algorithm descends to the (right) child, and now seeks to delete the node with value 4 in that subtree:
p->rchild = Delete(p->rchild, q->data);
In this new recursive call we get a new p again, which now refers to the node with value 5. We move left -- this assignment will play an important role later:
p->lchild = Delete(p->lchild, key);
This final recursive call has a new p that refers to the node with value 4 -- the one we were looking for.
This time we end up in the if block that has the delete, because this node is a leaf node. The node is freed and a null pointer is returned to the caller. From here on we start backtracking up the tree.
So one level up, at the node with value 5, we get the return value from the recursive call (which is a null pointer) and assign it:
p->lchild = Delete(p->lchild, key);
This important assignment will detach the duplicate node (with value 4) from the tree. You can see that if this assignment would not have been made, there would still be a reference to that node with a duplicate value -- even though it is pointing to freed memory.
The tree is now in its final shape:
7
/ \
4 8
/ \ \
1 5 9
/ \
0 6
Backtracking will still continue, going back to the root. Also there assignments are made to some child pointers, but these will not change the tree, as in all these cases we had returned return p;, which was the original value of the caller's child pointer.
Bug
As mentioned in the comments, the code has a bug. When deleting a leaf node, it does not verify that this node has actually the value to delete. And so in case you call this method with a value that does not occur in the tree, you'll end up deleting a leaf node with another value. In the example tree above: if you were to call Delete(root, 10), the node with value 9 will be deleted.
To correct this bug, move the following if block:
if (p->lchild == nullptr && p->rchild == nullptr){
... inside the outer else block, as the first statement there.

Reverse a LinkedList [duplicate]

This question already has answers here:
Create a reverse LinkedList in C++ from a given LinkedList
(10 answers)
Closed 7 years ago.
I'm trying to reverse a linked list and save it into another new liked list and delete the old one, but I get an error when compiling.. Here's the function..
LinkedList LinkedList::reverse(){
LinkedList L2;
Node * temp=head;
while(temp->next!=NULL){
Node * del=NULL;
L2.addAtFront(temp->data);
del=temp;
temp=temp->next;
delete del;
}
return L2;
}
Use this if (and only if) you no longer need the original linked list!
Instead of using addAtFront which is less cheap (because you need to allocate memory for new nodes and destroy the old ones), you can reuse the nodes and the LinkedList (after all, you were going to delete the original one, and simply set the pointers:
LinkedList LinkedList::reverse(){
Node* na = head;
Node* nb = NULL;
Node* nc = NULL;
if(na == NULL) {
return this;
}
nb = na->next;
na->next = NULL;
while(nb != NULL){
nc = nb->next;
nb->next = na;
na = nb;
nb = nc;
}
tail = head; //if you keep a tail?
head = na;
return this;
}
The method works as follows, you scan over the original list and use three references: na, nb and nc. Who are organized in the order of the original list.
Now you know for sure na and nb are effective in the while loop. You first ensure that you will keep a reference to nb's next by storing it in nc. Next you set the ->next of nb to na (originally na->next was nb), so now it is reversed.
Then you shift in the process: na becomes the old nb, nb the old nc. You keep repeating this until you reach the end of the linked list.
You need to do two additional tasks:
- Set the original head's ->next to null, otherwise you will construct a cycle; and
- Set the tail of the original LinkedList to the head.
If you maintain a tail, you will first need to set it to the head.

Is it possible to make efficient pointer-based binary heap implementations?

Is it even possible to implement a binary heap using pointers rather than an array? I have searched around the internet (including SO) and no answer can be found.
The main problem here is that, how do you keep track of the last pointer? When you insert X into the heap, you place X at the last pointer and then bubble it up. Now, where does the last pointer point to?
And also, what happens when you want to remove the root? You exchange the root with the last element, and then bubble the new root down. Now, how do you know what's the new "last element" that you need when you remove root again?
Solution 1: Maintain a pointer to the last node
In this approach a pointer to the last node is maintained, and parent pointers are required.
When inserting, starting at the last node navigate to the node below which a new last node will be inserted. Insert the new node and remember it as the last node. Move it up the heap as needed.
When removing, starting at the last node navigate to the second-to-last node. Remove the original last node and remember the the new last node just found. Move the original last node into the place of the deleted node and then move it up or down the heap as needed.
It is possible to navigate to the mentioned nodes in O(log(n)) time and O(1) space. Here is a description of the algorithms but the code is available below:
For insert: If the last node is a left child, proceed with inserting the new node as the right child of the parent. Otherwise... Start at the last node. Move up as long as the current node is a right child. If the root was not reached, move to the sibling node at the right (which necessarily exists). Then (whether or not the root was reached), move down to the left as long as possible. Proceed by inserting the new node as the left child of the current node.
For remove: If the last node is the root, proceed by removing the root. Otherwise... Start at the last node. Move up as long as the current node is a left child. If the root was not reached, move to the sibling left node (which necessarily exists). Then (whether or not the root was reached), move down to the right as long as possible. We have arrived at the second-to-last node.
However, there are some things to be careful about:
When removing, there are two special cases: when the last node is being removed (unlink the node and change the last node pointer), and when the second-to-last node is being removed (not really special but the possibility must be considered when replacing the deleted node with the last node).
When moving nodes up or down the heap, if the move affects the last node, the last-node pointer must be corrected.
Long ago I have made an implementation of this. In case it helps someone, here is the code. Algorithmically it should be correct (has also been subjected to stress testing with verification), but there is no warranty of course.
Solution 2: Reach the last node from the root
This solution requires maintaining the node count (but not parent pointers or the last node). The last (or second-to-last) node is found by navigating from the root towards it.
Assume the nodes are numbered starting from 1, as per the typical notation for binary heaps. Pick any valid node number and represent it in binary. Ignore the first (most significant) 1 bit. The remaining bits define the path from the root to that node; zero means left and one means right.
For example, to reach node 11 (=1011b), start at the root then go left (0), right (1), right (1).
This algorithm can be used in insert to find where to place the new node (follow the path for node node_count+1), and in remove to find the second-to-last-node (follow the path for node node_count-1).
This approach is used in libuv for timer management; see their implementation of the binary heap.
Usefulness of Pointer-based Binary Heaps
Many answers here and even literature say that an array-based implementation of a binary heap is strictly superior. However I contest that because there are situations where the use of an array is undesirable, typically because the upper size of the array is not known in advance and on-demand reallocations of an array are not deemed acceptable, for example due to latency or possibility of allocation failure.
The fact that libuv (a widely used event loop library) uses a binary heap with pointers only further speaks for this.
It is worth noting that the Linux kernel uses (pointer-based) red-black trees as a priority queue in a few cases, for example for CPU scheduling and timer management (for the same purpose as in libuv). I find it likely that changing these to use a pointer-based binary heap will improve performance.
Hybrid Approach
It is possible to combine Solution 1 and Solution 2 into a hybrid approach which dynamically picks either of the algorithms (for finding the last or second-to-last node), the one with a lower cost, measured in the number of edges that need to be traversed. Assume we want to navigate to node number N, and highest_bit(X) means the 0-based index of the highest-order bit in N (0 means the LSB).
The cost of navigating from the root (Solution 2) is highest_bit(N).
The cost of navigating from the previous node which is on the same level (Solution 1) is: 2 * (1 + highest_bit((N-1) xor N)).
Note that in the case of a level change the second equation will yield a wrong (too large) result, but in that case traversal from the root is more efficient anyway (for which the estimate is correct) and will be chosen, so there is no need for special handling.
Some CPUs have an instruction for highest_bit allowing very efficient implementation of these estimates. An alternative approach is to maintain the highest bit as a bit mask and do these calculation with bit masks instead of bit indices. Consider for example that 1 followed by N zeroes squared is equal to 1 followed by 2N zeroes).
In my testing it has turned out that Solution 1 is on average faster than Solution 2, and the hybrid approach appeared to have about the same average performance as Solution 2. Therefore the hybrid approach is only useful if one needs to minimize the worst-case time, which is (twice) better in Solution 2; since Solution 1 will in the worst case traverse the entire height of the tree up and then down.
Code for Solution 1
Note that the traversal code in insert is slightly different from the algorithm described above but still correct.
struct Node {
Node *parent;
Node *link[2];
};
struct Heap {
Node *root;
Node *last;
};
void init (Heap *h)
{
h->root = NULL;
h->last = NULL;
}
void insert (Heap *h, Node *node)
{
// If the heap is empty, insert root node.
if (h->root == NULL) {
h->root = node;
h->last = node;
node->parent = NULL;
node->link[0] = NULL;
node->link[1] = NULL;
return;
}
// We will be finding the node to insert below.
// Start with the current last node and move up as long as the
// parent exists and the current node is its right child.
Node *cur = h->last;
while (cur->parent != NULL && cur == cur->parent->link[1]) {
cur = cur->parent;
}
if (cur->parent != NULL) {
if (cur->parent->link[1] != NULL) {
// The parent has a right child. Attach the new node to
// the leftmost node of the parent's right subtree.
cur = cur->parent->link[1];
while (cur->link[0] != NULL) {
cur = cur->link[0];
}
} else {
// The parent has no right child. This can only happen when
// the last node is a right child. The new node can become
// the right child.
cur = cur->parent;
}
} else {
// We have reached the root. The new node will be at a new level,
// the left child of the current leftmost node.
while (cur->link[0] != NULL) {
cur = cur->link[0];
}
}
// This is the node below which we will insert. It has either no
// children or only a left child.
assert(cur->link[1] == NULL);
// Insert the new node, which becomes the new last node.
h->last = node;
cur->link[cur->link[0] != NULL] = node;
node->parent = cur;
node->link[0] = NULL;
node->link[1] = NULL;
// Restore the heap property.
while (node->parent != NULL && value(node->parent) > value(node)) {
move_one_up(h, node);
}
}
void remove (Heap *h, Node *node)
{
// If this is the only node left, remove it.
if (node->parent == NULL && node->link[0] == NULL && node->link[1] == NULL) {
h->root = NULL;
h->last = NULL;
return;
}
// Locate the node before the last node.
Node *cur = h->last;
while (cur->parent != NULL && cur == cur->parent->link[0]) {
cur = cur->parent;
}
if (cur->parent != NULL) {
assert(cur->parent->link[0] != NULL);
cur = cur->parent->link[0];
}
while (cur->link[1] != NULL) {
cur = cur->link[1];
}
// Disconnect the last node.
assert(h->last->parent != NULL);
h->last->parent->link[h->last == h->last->parent->link[1]] = NULL;
if (node == h->last) {
// Deleting last, set new last.
h->last = cur;
} else {
// Not deleting last, move last to node's place.
Node *srcnode = h->last;
replace_node(h, node, srcnode);
// Set new last unless node=cur; in this case it stays the same.
if (node != cur) {
h->last = cur;
}
// Restore the heap property.
if (srcnode->parent != NULL && value(srcnode) < value(srcnode->parent)) {
do {
move_one_up(h, srcnode);
} while (srcnode->parent != NULL && value(srcnode) < value(srcnode->parent));
} else {
while (srcnode->link[0] != NULL || srcnode->link[1] != NULL) {
bool side = srcnode->link[1] != NULL && value(srcnode->link[0]) >= value(srcnode->link[1]);
if (value(srcnode) > value(srcnode->link[side])) {
move_one_up(h, srcnode->link[side]);
} else {
break;
}
}
}
}
}
Two other functions are used: move_one_up moves a node one step up in the heap, and replace_node replaces moves an existing node (srcnode) into the place held by the node being deleted. Both work only by adjusting the links to and from the other nodes, there is no actual moving of data involved. These functions should not be hard to implement, and the mentioned link includes my implementations.
The pointer based implementation of the binary heap is incredibly difficult when compared to the array based implementation. But it is fun to code it. The basic idea is that of a binary tree. But the biggest challenge you will have is to keep it left-filled. You will have difficulty in finding the exact location as to where you must insert a node.
For that, you must know binary traversal. What we do is. Suppose our heap size is 6. We will take the number + 1, and convert it to bits. The binary representation of 7 is, "111". Now, remember to always omit the first bit. So, now we are left with "11". Read from left-to-right. The bit is '1', so, go to the right child of the root node. Then the string left is "1", the first bit is '1'. As you have only 1 bit left, this single bit tells you where to insert the new node. As it is '1' the new node must be the right child of the current node. So, the raw working of the process is that, convert the size of the heap into bits. Omit the first bit. According to the leftmost bit, go to the right child of the current node if it is '1', and to the left child of the current node if it is '0'.
After inserting the new node, you will bubble it up the heap. This tells you that you will be needing the parent pointer. So, you go once down the tree and once up the tree. So, your insertion operation will take O(log N).
As for the deletion, it is still a challenge to find the last node. I hope you are familiar with deletion in a heap where we swap it with the last node and do a heapify. But for that you need the last node, for that too, we use the same technique as we did for finding the location to insert the new node, but with a little twist. If you want to find the location of the last node, you must use the binary representation of the value HeapSize itself, not HeapSize + 1. This will take you to the last node. So, the deletion will also cost you O(log N).
I'm having trouble in posting the source code here, but you can refer to my blog for the source code. In the code, there is Heap Sort too. It is very simple. We just keep deleting the root node. Refer to my blog for explanation with figures. But I guess this explanation would do.
I hope my answer has helped you. If it did, let me know...! ☺
For those saying this is a useless exercise, there are a couple of (admittedly rare) use cases where a pointer-based solution is better. If the max size of the heap is unknown, then an array implementation will need to stop-and-copy into fresh storage when the array fills. In a system (e.g. embedded) where there are fixed response time constraints and/or where free memory exists, but not a big enough contiguous block, this may be not be acceptable. The pointer tree lets you allocate incrementally in small, fixed-size chunks, so it doesn't have these problems.
To answer the OP's question, parent pointers and/or elaborate tracking aren't necessary to determine where to insert the next node or find the current last one. You only need the bits in the binary rep of the heap's size to determine the left and right child pointers to follow.
Edit Just saw Vamsi Sangam#'s explanation of this algorithm. Nonetheless, here's a demo in code:
#include <stdio.h>
#include <stdlib.h>
typedef struct node_s {
struct node_s *lft, *rgt;
int data;
} NODE;
typedef struct heap_s {
NODE *root;
size_t size;
} HEAP;
// Add a new node at the last position of a complete binary tree.
void add(HEAP *heap, NODE *node) {
size_t mask = 0;
size_t size = ++heap->size;
// Initialize the mask to the high-order 1 of the size.
for (size_t x = size; x; x &= x - 1) mask = x;
NODE **pp = &heap->root;
// Advance pp right or left depending on size bits.
while (mask >>= 1) pp = (size & mask) ? &(*pp)->rgt : &(*pp)->lft;
*pp = node;
}
void print(NODE *p, int indent) {
if (!p) return;
for (int i = 0; i < indent; i++) printf(" ");
printf("%d\n", p->data);
print(p->lft, indent + 1);
print(p->rgt, indent + 1);
}
int main(void) {
HEAP h[1] = { NULL, 0 };
for (int i = 0; i < 16; i++) {
NODE *p = malloc(sizeof *p);
p->lft = p->rgt = NULL;
p->data = i;
add(h, p);
}
print(h->root, 0);
}
As you'd hope, it prints:
0
1
3
7
15
8
4
9
10
2
5
11
12
6
13
14
Sift-down can use the same kind of iteration. It's also possible to implement the sift-up without parent pointers using either recursion or an explicit stack to "save" the nodes in the path from root to the node to be sifted.
A binary heap is a complete binary tree obeying the heap property. That's all. The fact that it can be stored using an array, is just nice and convenient. But sure, you can implement it using a linked structure. It's a fun exercise! As such, it is mostly useful as an exercise or in more advanced datastructures( meldable, addressable priority queues for example ), as it is quite a bit more involved than doing the array version. For example, think about siftup/siftdown procedures, and all the edge cutting/sewing you'll need to get right. Anyways, it's not too hard, and once again, good fun!
There are a number of comments pointing out that by a strict definition it is possible to implement a binary heap as a tree and still call it a binary heap.
Here is the problem -- there is never a reason to do so since using an array is better in every way.
If you do searches to try to find information on how to work with a heap using pointers you are not going to find any -- no one bothers since there is no reason to implement a binary heap in this way.
If you do searches on trees you will find lots of helpful materials. This was the point of my original answer. There is nothing that stops people from doing it this way but there is never a reason to do so.
You say -- I have to do so, I've got an legacy system and I have pointers to nodes I need to put them in a heap.
Make an array of those pointers and work with them in this array as you would a standard array based heap, when you need the contents dereference them. This will work better than any other way of implementing your system.
I can think of no other reason to implement a heap using pointers.
Original Answer:
If you implement it with pointers then it is a tree. A heap is a heap because of how you can calculate the location of the children as a location in the array (2 * node index +1 and 2 * node index + 2).
So no, you can't implement it with pointers, if you do you've implemented a tree.
Implementing trees is well documented if you search you will find your answers.
I have searched around the internet (including SO) and no answer can be found.
Funny, because I found an answer on SO within moments of googling it. (Same Google search led me here.)
Basically:
The node should have pointers to its parent, left child, and right child.
You need to keep pointers to:
the root of the tree (root) (duh)
the last node inserted (lastNode)
the leftmost node of the lowest level (leftmostNode)
the rightmost node of the next-to-lowest level (rightmostNode)
Now, let the node to be inserted be nodeToInsert. Insertion algorithm in pseudocode:
void insertNode(Data data) {
Node* parentNode, nodeToInsert = new Node(data);
if(root == NULL) { // empty tree
parent = NULL;
root = nodeToInsert;
leftmostNode = root;
rightmostNode = NULL;
} else if(lastNode.parent == rightmostNode && lastNode.isRightChild()) {
// level full
parentNode = leftmostNode;
leftmostNode = nodeToInsert;
parentNode->leftChild = nodeToInsert;
rightmostNode = lastNode;
} else if (lastNode.isLeftChild()) {
parentNode = lastNode->parent;
parentNode->rightChild = nodeToInsert;
} else if(lastNode.isRightChild()) {
parentNode = lastNode->parent->parent->rightChild;
parentNode->leftChild = nodeToInsert;
}
nodeToInsert->parent = parentNode;
lastNode = nodeToInsert;
heapifyUp(nodeToInsert);
}
Pseudocode for deletion:
Data deleteNode() {
Data result = root->data;
if(root == NULL) throw new EmptyHeapException();
if(lastNode == root) { // the root is the only node
free(root);
root = NULL;
} else {
Node* newRoot = lastNode;
if(lastNode == leftmostNode) {
newRoot->parent->leftChild = NULL;
lastNode = rightmostNode;
rightmostNode = rightmostNode->parent;
} else if(lastNode.isRightChild()) {
newRoot->parent->rightChild = NULL;
lastNode = newRoot->parent->leftChild;
} else if(lastNode.isLeftChild()) {
newRoot->parent->leftChild = NULL;
lastNode = newRoot->parent->parent->leftChild->rightChild;
}
newRoot->leftChild = root->leftChild;
newRoot->rightChild = root->rightChild;
newRoot->parent = NULL;
free(root);
root = newRoot;
heapifyDown(root);
}
return result;
}
heapifyUp() and heapifyDown() shouldn’t be too hard, though of course you’ll have to make sure those functions don’t make leftmostNode, rightmostNode, or lastNode point at the wrong place.
TL;DR Just use a goddamn array.

Memory not freed up after deleting linked list's last node

I coded the following. Insert, delete at beginning, insert at beginning and end are all working fine. The memory marked in bold is not freed up. Should cout<<temp give an error? Please comment on the correctness of this code.
void del(node** head)
{
node* temp = (*head)->next;
node* prev = *head;
while(temp ->next!= NULL)
{
prev = temp;
temp = temp -> next;
}
cout<<endl<<temp;
cout<<endl<<prev;
//delete prev ->next;
prev -> next = 0;
delete temp;
cout<<endl<<"temp after free"<<temp;
cout<<endl<<prev;
}
void main()
{
node* head = NULL;
int x = 5;
head = insert(head,x);
insert(head,6);
insert(head,7);
insert(head,8);
print(head);
del(&head);
print(head);
getch(); }
Output:
Empty List
|5| at 00673BF0
-------------------
|6| at 00673C30
-------------------
|7| at 00673FB8
-------------------
|8| at 00673FF8
-------------------
00673FF8
00673FB8
temp after free00673FF8
00673FB8
|5| at 00673BF0
-------------------
|6| at 00673C30
-------------------
|7| at 00673FB8
-------------------
delete does not set the value of the pointer to NULL, but the memory pointed to is no longer valid (does not contain a live node)
This means that cout << temp will print the value of the pointer it still has (and was the address of the node before the delete), but dereferencing temp (e.g. *temp or temp->next) is undefined behavior
Note: nowehere in del do you modify the pointer pointed to by head, so you either don't need the double indirection (node**) or you should assign the new head to *head.
As Attila already pointed out it's undefined behavior to dereference a pointer after deleting it. To prevent accidentally doing that it's good practice to assign NULL to the pointer directly after the delete. Dereferencing a NULL pointer immediately causes an error pointing you to the right cause.
In the code its good to free the temp node first. Then assign the prev->next to NULL. So that it will be very easy to understand that temp node is not available, thats way making prev->next NULL.