Sentinel Node in a C Linked List - c

I'm trying to learn more about linked lists in C and I recently stumbled upon the Sentinel Node concept, but I can't wrap my head around it. According to the slides I have, the sentinel node should be the first thing on the list when it's created and the last when other nodes are added. There should be a pointer to permanently point to the Sentinel Node.
All those stuff confuse me and I would love some help with the implementation.
/*this is a simple LL implementation*/
#include <stdio.h>
#include <stdlib.h>
struct List
{
int data;
struct List *next;
};
void ListInsert(int new_data)
{
struct List *p;
p = (struct List *)malloc(sizeof(struct List));
p->data = new_data;
p->next = (head);
head = p;
}
void printList(struct List *q)
{
q = head;
while (q != NULL)
{
printf("%d ", q->data);
q = q->next;
}
printf("\n");
}
int main()
{
ListInsert(5);
ListInsert(7);
ListInsert(6);
ListInsert(4);
ListInsert(2);
printList(head);
return 0;
}
Now, if I want to create the sentinel node, how should I proceed?

According to the slides i have, the sentinel node should be the first
thing on the list when its created and the last when other nodes are
added.There should be a pointer to permanently point to the Sentinel
Node.
Let's start with the most important point: the purpose of a sentinel node, which is to mark the end of the list. There will not be real data associated with a sentinel node, so a list containing only a sentinel node is logically empty.
A few things follow from that, including:
the identity of the sentinel node is a property of the whole list, not of any (other) particular node
list manipulation algorithms need to be written differently for linked lists whose ends are marked by a sentinel than for those whose ends are marked by some other means.
each list need a place to store the sentinel's identity
a list that is expected to have a sentinel is invalid if it does not have one
There are many ways to implement the details, all with their own advantages and disadvantages.
Personally, I would be inclined (in the non-sentinel case, too) to have a structure to represent an overall list, separate from the structure used to represent a list node. A pointer to the list's head node would be a member of this structure, and in the sentinel-terminated-list case, so would be a pointer to the sentinel node. When you create a new list, you create a sentinel node for it, too; initially, the list's head and sentinel pointers will both point to that node. The head pointer may be changed, but the sentinel pointer must not be. When you append to the list, the appended node gets placed just before the sentinel. It is an error to try to delete the sentinel from the list.
It is to your advantage to write the code for this yourself.

Create it. You said "There should be a pointer to permanently point to the Sentinel Node", so create the pointer. Then use the pointer as the terminator of the list instead of NULL.
Sentinel node - Wikipedia
/*this is a simple LL implementation*/
#include <stdio.h>
#include <stdlib.h>
struct List
{
int data;
struct List *next;
};
struct List sentinel_node_instance;
/* a pointer to permanently point to the Sentinel Node */
struct List* const SENTINEL_NODE = &sentinel_node_instance;
/* the sentinel node should be the first thing on the list when it's created */
struct List* head = SENTINEL_NODE;
void ListInsert(int new_data)
{
struct List *p;
p = (struct List *)malloc(sizeof(struct List));
p->data = new_data;
p->next = (head);
head = p;
}
void printList(void)
{
struct List* q = head;
while (q != SENTINEL_NODE)
{
printf("%d ", q->data);
q = q->next;
}
printf("\n");
}
int main()
{
ListInsert(5);
ListInsert(7);
ListInsert(6);
ListInsert(4);
ListInsert(2);
printList();
return 0;
}

Another variation of a sentinel node is for a circular doubly linked list, where it is both a head node and a sentinel node. Visual Studio implements std::list in this manner.
head.next = pointer to first node or to head if empty list
head.prev = pointer to last node or to head if empty list
first.prev = pointer to head node
last.next = pointer to head node

Related

How do I delete a doubly linked list in C?

I've created a doubly linked list, filled it with values and now I want to delete it and remove all the values to avoid memory leaks. Here's what I wrote as well as the structs that were used when creating the doubly linked list. Both those functions will be called towards the end of the main function.
struct node
{
struct node *next;
struct node *prev;
char *value;
};
// The type for a list.
typedef struct list
{
struct node head;
} List;
// The type for a list position.
typedef struct list_pos
{
struct node *node;
} ListPos;
void list_destroy(List *lst)
{
List p,q;
p = *lst;
while (p)
{
q = p.head->next;
free(p);
p = q;
}
*lst = NULL;
}
// Remove the value at the position and return the position of the next element.
ListPos list_remove(ListPos pos)
{
}
You appear to have the right general idea: you walk the list and free each node, making sure to grab any needed data from each node (in particular, the pointer to the next node) while the node holding it still exists. Your case differs from some that you might have seen, however, because instead of handling the overall list via a bare pointer to the head node, you have a separate object, of a separate type (List / struct list), to represent the list itself. This approach has much to recommend it, including, especially, the use of (apparently) a dummy head node, which provides for a variety of algorithmic simplifications. This is usually how I write a linked list.
But because struct list is not struct node, you cannot set a list pointer equal to a node pointer. Instead, create a struct node * to track your position. The first node to free would be the one referenced by struct node *to_free = lst->head.next, and the one after that would be the one referenced by to_free->next.
Note that you might need to free the struct list, too.

Reverse the pointers in a linked list

Hello guys could you please help me in writing a procedure to reverse the pointers in a linked list . for example A->B->C->D would become A<-B<-C<-D without using extra linked list .
Edit:
------ okay guys so i have been looking for solution for this problem here is the code in case u want it :
void reverse_list(){
struct node *next, *current,*previous;
previous = NULL;
current =head;
while(current != NULL){
next = current->next;
current->next = previous;
previous=current;
current = next;
}
head = previous;
}
You could think of the list as a stack. Then you could easily reverse such a list by "popping" the nodes and "pushing" them into a new list.
The above could be done both destructively (destroying the old list) and non-destructively (creating the new list as a reversed copy of the original list).
As you didn't mention if you are implementing the linked list yourself or not.
So firstly I am assuming that you are doing it by yourself. So following is an implementation of linked list and again its pointers are reversed to make the link list reverse. You can take an idea from it.
#include<stdio.h>
#include<stdlib.h>//for using malloc
struct Node//Defining a structure for linked list's node
{
int info;//assuming it is an integer linked list
struct Node *link;//this pointer links a node to it's immediate neighbor node
};
struct Node *head=NULL,*temp;//some initializations and declarations
void insertion(int data)//To insert elements to linked list
{
struct Node *ptr;
ptr=malloc(sizeof(*ptr));//creating a new node for the newcomer
ptr->info=data;//taking the given integer value for the node to hold
//initializing with null as the current node may be the last node and
//if it is then it will point nobody
//...but if it is not when a new node comes in the future it will eventually be
//replaced to point the newcomer
ptr->link=NULL;
if(head==NULL)//head still null means we are creating the first node
{ //handling the head separately as it has no parent node
head=ptr;
temp=ptr;//copying the current node's pointer to temp such that we can
//find it as a parent of next node
}
else//for the rest of the nodes' creation
{
//as temp is the pointer to the previous node, so previous node is linking
//to its next node, i.e, the current node
temp->link=ptr;
//updating the temp to point the current node such that it can act as a parent node
//when the next node comes
temp=ptr;
}
}
void reversePointers()
{
struct Node *trav,*from=NULL,*temp;
for(trav=head;;)
{
if(trav->link==NULL)//if we have reached to the end
{
head=trav;//then the reverse linked list's head should point to the last element
trav->link=from;//and making the second last node as it's next node
break;
}
temp=trav;//saving current node pointer to update the "from" pointer
trav=trav->link;//advancing current node pointer to forward
temp->link=from;//making the current node to point to it's previous node
from=temp;//saving current node's pointer which will be used in next iteration
}
}
void traverse()//to traverse the nodes
{
struct Node *ptr=head;
while(ptr!=NULL)
{
printf("%d ",ptr->info);
ptr=ptr->link;
}
printf("\n");
}
int main(void)
{
int i,n,t;
printf("Enter Number of elements: ");
scanf("%d",&n);
printf("Enter Elements: ");
for(i=0;i<n;i++)
{
scanf("%d",&t);
insertion(t);
}
printf("Before reversing the pointers the elements are: ");
traverse();
//let's reverse the pointers to make the list to go backward
reversePointers();
printf("After reversing the pointers the elements are: ");
traverse();
}
Secondly if you are using STL list then the approach is quite straightforward. Just use,
your_list_name.reverse()
Again if you want to reverse the STL list just for iteration purpose then there is no need to actually reverse it. Instead you can use reverse iterator as following (say for an integer list):
for(list<int>::reverse_iterator it=your_list_name.rbegin();it!=your_list_name.rend();it++)
{
//do whatever you want
}

Understanding code for creating a singly linked list using double pointer in C

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.

Understanding the logic behind building linked list using local reference

Below is the code for creation of linked list using local reference logic.
Not able to understand the code inside the for loop especially the 2nd line. (see // HERE)
Can somebody please elaborate how this logic is working.
void push(struct Node** head_ref, int new_data)
{
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = new_data;
newNode->next = *head_ref;
*head_ref = newNode;
return;
}
struct Node* buildWithLocalRef()
{
int i=0;
struct Node *head = NULL;
struct Node **lastptrRef = &head;
for(i=1;i<6;i++)
{
push(lastptrRef,i);
lastptrRef = &((*lastptrRef)->next); // HERE
}
return head;
}
int main()
{
struct Node* head;
head = buildWithLocalRef();
printList(head);
return 0;
}
The technique you're seeing is building a linked list by forward-chaining. It is the most direct, and sensible way to build an ordered list from beginning to end, where the list does not have a tail pointer (and yours does not).
There are no "references" here. This isn't C++. This is using a pointer to pointer. The variable name is dreadfully named, btw. How it works is this:
Initially the list is empty, head is NULL
A pointer to pointer, lastptrRef will always hold the address of (not the address in; there is a difference) the next pointer to populate with a new dynamic node allocation. Initially that pointer-to-pointer holds the address of the head pointer, which is initially NULL (makes sense, that is where you would want the first node hung).
As you iterate the loop a new node is allocated in push . That node's next pointer is set to whatever value is in the pointer pointed to by lastptrRef (passed as head_ref in the function), then the pointer pointed to by lastptrRef is updated to the new node value.
Finally, lastptrRef is given the address of the next member in the node just added, and the process repeats.
In each case, lastptrRef hold the address of a pointer containing NULL on entry into push. This push function makes this harder to understand. (more on that later). Forward chaining is much easier to understand when done directly, and in this case, it would make it much, much easier to understand
struct Node* buildWithLocalRef()
{
struct Node *head = NULL;
struct Node **pp = &head;
for (int i = 1; i < 6; i++)
{
*pp = malloc(sizeof **pp);
(*pp)->data = i;
pp = &(*pp)->next;
}
*pp = NULL;
return head;
}
Here, pp always holds the address of the next pointer we'll populate with a new node allocation. Initially, it holds the address of head. As each node is inserted pp is set to the address of the next pointer within the latest node inserted, thereby giving you the ability to continue the chain on the next iteration. When the loop is done, pp holds the address of the next pointer in the last node in the list (or the address of head of nothing was inserted; consider what happens if we just pull the loop out entirely). We want that to be NULL to terminate the list, so the final *pp = NULL; is performed.
The code you posted does the same thing, but in a more convoluted manner because push was designed to push items into the front of a list (apparently). The function always sets the pointer pointed to by head_ref to the new node added, and the node's next is always set to the old value in *head_ref first. Therefor, one can build a stack by doing this:
struct Node* buildStack()
{
struct Node *head = NULL;
for (int i = 1; i < 6; i++)
push(&head, i);
return head;
}
Now if you print the resulting linked list, the number will be in reverse order of input. Indeed, push lives up to its name here. Dual-purposing it to build a forward-chained list is creative, I'll grant that, but in the end it makes it somewhat confusing.

What more does the code need to delete a node from a linked list successfully?

I want to delete a given node from a linked list by the node's index number (serial number). So what I tried to do in my function is that, first I have taken the user input of the index number. Then I used two node type pointers temp and current. I started traversing the list with current and when the index number of the node matches with the user input, I tried to delete the node. So far it is correct. I am facing problem with the deletion logic. Here is the code I tried:
void delete_node(struct node **start,int index_no)
{
int counter=0;
struct node *temp, *current;
temp=(struct node *)malloc(sizeof(struct node));
current=(struct node *)malloc(sizeof(struct node));
current=*start;
while(current->next!=NULL)
{
counter++;
if(counter==index_no)
{
temp= current->next;
free(current);
/*I guess some code is missing here. Help me finding the logic.*/
}
else
{
printf("\n The index number is invalid!!");
}
}
}
The commented portion lacks the deletion logic.
Also, I have a feeling that this code is not space and time-efficient. If it is so, please suggest to a way to make it more compact.
Why are you allocating two nodes in the delete function, then leaking their memory? It seems they should be initialized to start or one of its successors.
You also need to update the next pointer in the previous element and potentially also the start (head) of the list if the removed element was the first (ie. index_no == 1).
You also have an off-by-one error where the final node can never be deleted, because only a node with a ->next pointer will be considered for deletion.
Suggested reading: A Tutorial on Pointers and Arrays in C.
Deleting from a linked list is actually:
find the pointer that points to us
(if found) make it point to our .next pointer instead
delete our node.
In order to change the pointer that points to us, we need a pointer to it: a pointer to pointer. Luckily the first argument already is a pointer to pointer, it presumably points to the head pointer that points to the first list item.
struct node
{
struct node *next;
int num;
} ;
void delete(struct node **pp, int num) {
struct node *del;
int counter;
for (counter=0; *pp; pp= &(*pp)->next) {
if(counter++ == num) break;
}
if (!*pp) { printf("Couldn't find the node(%d)\n", num); return; }
/* if we get here, *pp points to the pointer that points to our current node */
del = *pp;
*pp = del->next;
free(del);
}

Resources