This appeared on one of the old exams on algorithms and data structure. It seems pretty simple but I need some help with understanding why it works.
The goal is to delete certain atoms from a singly linked list using only a pointer to head.
The structure of atom is:
struct at {
int element;
struct at *next;
};
typedef struct at atom;
The solution is:
void delete(atom **head)
{
while((*head)){
if((*head)->element%2){ /*just a condition for deleting*/
(*head)=(*head)->next; /*deleting the atom*/
} else {
head= &(*head)->next;
}
}
}
What I understand is that, in this function, "*head" is the actual head (pointer to the first atom) and "head" is a pointer to the actual head. Clearly, since I'll actually change the head and contents of the list, I need to pass a pointer to head.
I just can't understand how head= &(*head)->next seems to work. I've tried putting it down on paper and still can't make any sense of it. How does it not change anything and just jump to the next atom?
As you said *head is the actual head (pointer to the first atom) and head is a pointer to the actual head.In statement head= &(*head)->next;
we are updating head (which contains address of the actual head) with the address of address of node next to actual head node.
Now head is a pointer which store the address of head to next node not head node i.e next atom.
Consider a list
1->2->3->4->5
in this case initially head contains the address node 1, and *head is node 1 itself.
Now when we say head= &(*head)->next; it means head will store address of node 2.If we will do *head it return node 2.
Related
I am trying to understand how the code below for creating a singly linked list works using a double pointer.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
void push(struct Node** headRef, int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
//Function to implement linked list from a given set of keys using local references
struct Node* constructList(int keys[], int n) {
struct Node *head = NULL;
struct Node **lastPtrRef = &head;
int i, j;
for(i = 0; i < n; i++) {
push(lastPtrRef, keys[i]);
lastPtrRef = &((*lastPtrRef)->next); //this line
if((*lastPtrRef) == NULL) {
printf("YES\n");
}
}
return head;
}
int main() {
int keys[] = {1, 2, 3, 4};
int n = sizeof(keys)/sizeof(keys[0]);
//points to the head node of the linked list
struct Node* head = NULL;
head = constructList(keys, n); //construct the linked list
struct Node *temp = head;
while(temp != NULL) { //print the linked list
printf(" %d -> ", temp->data);
temp = temp->next;
}
}
I understand the purpose of using the double pointer in the function push(), it allows you to change what the pointer headRef is pointing to inside the function. However in the function constructList(), I don't understand how the following line works:
lastPtrRef = &((*lastPtrRef)->next);
Initially lastPtrRef would be pointing to head which points to NULL. In the first call to push(), within the for loop in constructList(), the value that head points to is changed (it points to the new node containing the value 1). So after the first call to push(), lastPtrRef will be pointing to head which points to a node with the value of 1. However, afterwards the following line is executed:
lastPtrRef = &((*lastPtrRef)->next);
Whereby lastPtrRef is given the address of whatever is pointed to by the next member of the newly added node. In this case, head->next is NULL.
I am not really sure what the purpose of changing lastPtrRef after the call to push(). If you want to build a linked list, don't you want lastPtrRef to have the address of the pointer which points to the node containing 1, since you want to push the next node (which will containing 2) onto the head of the list (which is 1)?
In the second call to push() in the for loop in constructList, we're passing in lastPtrRef which points to head->next (NULL) and the value 2. In push() the new node is created, containing the value 2, and newNode->next points to head->next which is NULL. headRef in push gets changed so that it points to newNode (which contains 2).
Maybe I'm understanding the code wrong, but it seems that by changing what lastPtrRef points to, the node containing 1 is getting disregarded. I don't see how the linked list is created if we change the address lastPtrRef holds.
I would really appreciate any insights as to how this code works. Thank you.
This uses a technique called forward-chaining, and I believe you already understand that (using a pointer-to-pointer to forward-chain a linked list construction).
This implementation is made confusing by the simple fact that the push function seems like it would be designed to stuff items on the head of a list, but in this example, it's stuffing them on the tail. So how does it do it?
The part that is important to understand is this seemingly trivial little statement in push:
newNode->next = *headRef
That may not seem important, but I assure you it is. The function push, in this case, does grave injustice to what this function really does. In reality it is more of a generic insert. Some fact about that function
It accepts a pointer-to-pointer headRef as an argument, as well as some data to put in to the linked list being managed.
After allocating a new node and saving the data within, it sets the new node's next pointer to whatever value is currently stored in the dereferenced headRef pointer-to-pointer (so.. a pointer) That's what the line I mentioned above accomplishes.
It then stores the new node's address at the same place it just pulled the prior address from; i.e. *headRef
Interestingly, it has no return value (it is void) further making this somewhat confusing. Turns out it doesn't need one.
Upon returning to the caller, at first nothing may seem to have changed. lastPtrRef still points to some pointer (in fact the same pointer as before; it must, since it was passed by value to the function). But now that pointer points to the new node just allocated. Further, that new node's next pointer points to whatever was in *lastPtrRef before the function call (i.e. whatever value was in the pointer pointed to by lastPtrRef before the function call).
That's important. That is what that line of code enforces, That means if you invoke this with lastPtrRef addressing a pointer pointing to NULL (such as head on initial loop entry), that pointer will receive the new node, and the new node's next pointer will be NULL. If you then change the address in lastPtrRef to point to the next pointer of the last-inserted node (which points to NULL; we just covered that), and repeat the process, it will hang another node there, setting that node's next pointer to NULL, etc. With each iteration, lastPtrRef addresses the last-node's next pointer, which is always NULL.
That's how push is being used to construct a forward linked list. One final thought. What would you get for a linked list if you had this:
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node* next;
};
void push(struct Node** headRef, int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
int main()
{
//points to the head node of the linked list
struct Node* head = NULL;
push(&head, 1);
push(&head->next, 2);
push(&head->next, 3);
for (struct Node const *p = head; p; p = p->next)
printf("%p ==> %d\n", p, p->data);
}
This seemingly innocent example amplifies why I said push is more of a generic insert than anything else. This just populates the initial head node.
push(&head, 1);
Then this appends to that node by using the address of the new node's next pointer as the first argument, similar to what your constructList is doing, but without the lastPtrRef variable (we don't need it here):
push(&head->next, 2);
But then this:
push(&head->next, 3);
Hmmm. Same pointer address as the prior call, so what will it do? Hint: remember what that newNode->next = *headRef line does (I droned on about it forever; I hope something stuck).
The output of the program above is this (obviously the actual address values will be different, dependent to your instance and implementation):
0x100705950 ==> 1
0x10073da90 ==> 3
0x100740b90 ==> 2
Hope that helps.
For my program, I need to create a function that accepts a linkedlist as a parameter, then deletes the first node from the list. There are other steps, but I'd like to get this sub-part done first.
This is what I have so far:
struct node *del_the_first(struct node *head) {
struct node *temp = head;
head = head->next;
temp->next = NULL;
return NULL;
}
I believe my solution is correct, however I have no way of testing it at this time. I'm more interested in why I am or am not wrong.
What you should test is:
print the value of temp at the end of the function,
this is what head was at the start of the function
print the value of head at the end of the function,
which is what the head of the list should be after returning for the function
print (from outside the function, e.g. from main) the value of the variable
which is supposed to point to the head of the list,
especially after deleting the first element
You will notice that outside your function the pointer to the head of the list is still pointing to where the first element still is.
You do not want that, do you? The variable which points to the head of the list is supposed to point to the second element of the list, isn't it?
If above is true, you probably want to use free() on the formerly first element of the list, before returning from the function.
Read this for more information on how to fix the first problem:
Parameter Passing in C - Pointers, Addresses, Aliases
Basically, you will want to return the new value of the pointer to the head of the list:
struct node *del_the_first(struct node *head)
{
struct node *temp = head;
head = head->next;
temp->next = NULL; /* not really needed */
free(temp);
return head;
}
Then call it like:
global_head = del_the_first(global_head);
Note that this code assumes that the list is not empty,
see the answer by ccpan on how to remove this assumption.
You need to check for boundary conditions. Suppose your linkedList is empty, then during runtime, you will get a segmentation fault. So you need to check if head pointer is NULL or not before trying to access next node.
Also, I don't know why you are returning a NULL. You are most probably wanting to return the new head node pointer.
struct node *del_the_first(struct node *head) {
if (head != NULL) {
struct node *temp = head;
head = head->next;
free(temp);
temp = NULL;
}
return head;
}
struct node
{
int data;
struct node *next;
};
void addstart (struct node **n, int new_data){
struct node *new = (struct node*)malloc(sizeof(struct node));
new->data=new_data;
new->next=*n;
*n= new;
}
int main(){
struct node* head = NULL;
addstart(&head,5);
return 0;
}
i just want to know if head_node is also a new_node and both are same data type struct node then why we are defining it simply as
struct node* head = NULL;
and other nodes as:
struct node *new = (struct node*)malloc(sizeof(struct node));
struct node* head = NULL;
Because head is there to simply keeping track of the head of the list nothing else. Now don't the new node does the same?
yes it does. it keeps track of the memory that you allocate using malloc and then you point it out with subsequent node's next attribute.
head is of same type as newnode and both are same functonally. but we put a different meaning to head in that - we are making it point to the head of th list rather than making it a local variable which we can throw away.
Then also why head = NULL?
Ah! that's because we always create a node and make it's next attribute to point to the current head and then we change head to make the new node part of our linked list.
Initially the first node should be
+----+
| +------+
+----+ |
V
NULL (Well to make this NULL we just make head = NULL initially)
i want to know why we are not using malloc in head assignment case..
Because it's not needed - as simple as that. Why should we allocate an extra memory space and then assign it to head when the purpose of head is to just point to already allocated node. And when we add another extra node we add it to the head of the linked list and make it head. it's nothing other than pointing to the beginning of the list. That's it.
new is not a good variable name. Try some good one. Think a bit before naming a variable it will save you a lot of time in future.
Because when you declare head it doesn't have anything to point to yet, it indicates the list is empty , as soon as data is added it can point to the first node in the list.
I am learning about linked lists and how to create them in C with structs and pointers. I have an example below. From my understanding the called push() passes the beginning memory location of our struct where the head node lies as an argument. The parameter of our push() function takes a struct node as a pointer to pointer so it is passed as a reference, not an actual copy. So the first pointer of our struct node** headref is just a pointer to the memory location of our head node and the second pointer points to the value, which is the next memory location that the head node points to. We create a new node called newnode inside our struct node by assigning it some memory. We then create an int type data inside this node.
Okay assuming everything I said is correct, the next part is what i am confused on.
newNode->next= *headRef;
This line from what i can understand dereferences the headref so this would just have the headref pointing to the head node. Then we have a pointer operation where what the headref is pointing to will then also be what our pointer next will be pointing to. Based on that the pointer next in our new node(newnode) will be pointing to the head pointer.
The next line which i am also confused on is:
*headRef = newNode;
What the dereferenced headref pointer is pointing to, which is the head node, will be pointing now to our newnode.
Based on that there should be a new node called newnode with an int data and a next pointer linking our newnode to the head. Then the headref pointer(or is it the head node?) will point to the new node. I know this isn't correct because the pointer next of our newnode should be pointing to the second node so our newnode can be linked in the struct. I also don't believe that i am understanding the pointer to pointer and dereferencing in the above two lines of code.
Code:
void Push(struct node** headRef, int data) {
struct node* newNode = malloc(sizeof(struct node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
void PushTest(void) {
struct node* head = BuildTwoThree(); // suppose this returns the list {2, 3}
Push(&head, 1);
Push(&head, 13);
// head is now the list {13, 1, 2, 3}
}
void Push(struct node** headRef, int data) {
headRef contains an address of address where struct node is located. It is an address of the head variable in PushTest function.
struct node* newNode = malloc(sizeof(struct node));
Here we created a newNode. It contains an address of memory where node struct is located.
newNode->data = data;
set the data of newNode to the value of the data parameter passed into the Push function - OK.
newNode->next = *headRef;
set the newNode->next to the address of the structure located in the head variable. For example, if we have written
void Push(struct node* head, int data) {
...
newNode->next = head;
Next, we need to change the head variable to the newNode. If head variable was passed by reference, we could simply write this, like in C++:
void Push(struct node* &head, int data) {
...
head = newNode;
But in plain C we have to pass the address where head variable is located, so we can write into that address the pointer to the structure newNode we created in the Push function:
*headRef = newNode;
is equivalent to the write the newNode pointer to the variable whose address is located inside the headRef variable
And there is a problem in your logic:
The parameter of our push() function takes a struct node as a pointer
to pointer so it is passed as a reference, not an actual copy.
Actually, we pass a copy of temporary variable that contains an address of node variable, not the reference. There is pass-by-reference method in C++, it uses ampersand to declare that the variable passed to function is a reference, not a copy of original variable.
You are actually both correct and not correct at the same time. The push function adds a new node at the head of the list. After this function is called the new node is the new head, and the previous head node is now the second (next) node in the list. So your observations are correct, but your conclusion is not.
I recommend you step through the code in a debugger, while watching all pointers and their contents, to see what happens. It might make things clearer for you.
Ok. I think I can explain.
head is the head pointer of linked list. headRef points to head. So *headRef is head pointer(pass by reference).
so newNode->next will now point to head of the struct ( which has value 2, thats why the nodes are getting inserted at front).
Now in this line *headRef = newNode;
*headRef is assigned the value of newNode so the head is now changed to newNode in the original structure.
Again when you pass &head, you are passing the head which contains value 1 now.
Let's copy this here, for simpler reference:
1 void Push(struct node** headRef, int data) {
2 struct node* newNode = malloc(sizeof(struct node));
3 newNode->data = data;
4 newNode->next = *headRef;
5 *headRef = newNode;
6 }
For the sake of simplicity, I numbered the lines.
Now, headRef itself is just a pointer to the variable that held the header of the list -- therefore, it contains no useful information by itself. By dereferencing that pointer, you're gaining access to the contents of the variable itself (head in PushTest()). Therefore, *headRef is essentially the way you get access to head. Now, since head is the top node, so is *headRef -- they hold the same value, since they are the same address. What line 4 does is, assign the value of *headRef (i.e., the value of head) to the new node's next link. In the same fashion, line 5 assigns the new node to *headRef, which is the same as assigning it to head, since headRef is a pointer to head -- meaning that *headRef is the value of head.
The key point here is, headRef is a reference (a pointer) to head, so *headRef and head are equivalents (scope rules aside).
Here's my function to delete a linked list:
void deleteList( NODE* head )
{
NODE* temp1;
NODE* tempNext;
temp1 = head;
tempNext = NULL;
while( temp1 != NULL )
{
tempNext = temp1->next;
free(temp1);
temp1 = tempNext;
}
}
So temp1 first points where the head pointer is pointing. If it isn't NULL, tempNext will be set to point to the next element of the list. Then the first element (temp1) is free'd, and temp1 is reassigned to point to where tempNext is pointing and process repeats.
Is this the right approach to deleting an entire list?
I ask this because when I print the list after using this function, it still prints the list. And IIRC freeing something doesn't delete it but only marks it as available so I'm not sure how to tell if this is correct or not.
Your code looks correct.
You're also correct that freeing a list's elements doesn't immediately change the memory they pointed to. It just returns the memory to the heap manager which may reallocate it in future.
If you want to make sure that client code doesn't continue to use a freed list, you could change deleteList to also NULL their NODE pointer:
void deleteList( NODE** head )
{
NODE* temp1 = *head;
/* your code as before */
*head = NULL;
}
It still print the list, because you probably don't set the head pointer to NULL after calling this function.
I ask this because when I print the list after using this function, it still prints the list.
There is a difference between freeing a pointer and invalidating a pointer. If you free your whole linked list and the head, it means that you no longer "own" the memory at the locations that head and all the next pointers point to. Thus you can't garintee what values will be there, or that the memory is valid.
However, the odds are pretty good that if you don't touch anything after freeing your linked list, you'll still be able to traverse it and print the values.
struct node{
int i;
struct node * next;
};
...
struct node * head = NULL;
head = malloc(sizeof(struct node));
head->i = 5;
head->next = NULL;
free(head);
printf("%d\n", head->i); // The odds are pretty good you'll see "5" here
You should always free your pointer, then directly set it to NULL because in the above code, while the comment is true. It's also dangerous to make any assumptions about how head will react/contain after you've called free().
This is a pretty old question, but maybe it'll help someone performing a search on the topic.
This is what I recently wrote to completely delete a singly-linked list. I see a lot of people who have heartburn over recursive algorithms involving large lists, for fear of running out of stack space. So here is an iterative version.
Just pass in the "head" pointer and the function takes care of the rest...
struct Node {
int i;
struct Node *next;
};
void DeleteList(struct Node *Head) {
struct Node *p_ptr;
p_ptr = Head;
while (p_ptr->next != NULL) {
p_ptr = p_ptr->next;
Head->next = p_ptr->next;
free(p_ptr);
p_ptr = Head;
}
free(p_ptr);
}