I would very much appreciate it if anybody could explain this solution line by line with visualization.
Let's take an example linked list with values 1, 2, and 3, where head
points to its first node, and we have a value n
that equals 2. We can picture the state of the list in the main program as follows:
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────►│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └──────────────┘
With this solution code, the caller must pass a pointer to a pointer to a node, and so if the head
variable in the main program is a pointer to the first node, we need to pass its address, like so:
delete_from_list(&head, n);
Let's now picture how the function sees its own list
variable:
┌───────┐
│ list │
└───│───┘
▼
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────►│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └──────────────┘
The first statement is:
struct node *item = *list;
So we get:
┌───────┐ ┌───────┐
│ list │ │ item │
└───│───┘ └───│───┘
▼ ▼
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────►│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
└─────────────┘ └─────────────┘ └──────────────┘
Execution enters the loop, but the if
condition is false, so the next executed statement is:
list = &item->next;
Now the local list
variable will point to the address of the next
member of the node that item
points to:
┌───────┐
│ item │
└───│───┘
▼
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────►│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ▲ │ │ │ │ │
└───│─────────┘ └─────────────┘ └──────────────┘
│
┌───│───┐
│ list │
└───────┘
The last statement in the loop's body is:
item = item->next;
So we get:
┌───────┐
│ item │
└───│───┘
▼
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────►│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ▲ │ │ │ │ │
└───│─────────┘ └─────────────┘ └──────────────┘
│
┌───│───┐
│ list │
└───────┘
In the next iteration the if
condition is true, so then we execute this:
*list = item->next;
Which mutates the list as follows:
┌───────┐
│ item │
└───│───┘
▼
┌───────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 2 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next────────┐│ │ next────────►│ │ next NULL│ │
│ └─────────┘ │ ││ └─────────┘ │ │ └──────────┘ │
│ ▲ │ ││ │┌►│ │
└───│─────────┘ │└─────────────┘│ └──────────────┘
│ └───────────────┘
┌───│───┐
│ list │
└───────┘
Now the found node can be freed with free(item)
:
┌───────┐
│ item │
└───│───┘
▼
┌───────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next─────────────────────────►│ │ next NULL│ │
│ └─────────┘ │ │ └──────────┘ │
│ ▲ │ │ │
└───│─────────┘ └──────────────┘
│
┌───│───┐
│ list │
└───────┘
Finally the function returns *list
, but that is of little use: the caller should not use that return value for assignment to its own head
variable, or things would go bad. The caller should just rely on the function to adapt its head
if necessary (not the case in this example).
This is what the caller ends up with:
┌───────┐ ┌─────────────┐ ┌──────────────┐
│ head ────►│ ┌─────────┐ │ │ ┌──────────┐ │
└───────┘ │ │ value 1 │ │ │ │ value 3 │ │
│ └─────────┘ │ │ └──────────┘ │
│ ┌─────────┐ │ │ ┌──────────┐ │
│ │ next─────────────────────────►│ │ next NULL│ │
│ └─────────┘ │ │ └──────────┘ │
│ │ │ │
└─────────────┘ └──────────────┘
Remarks
In my opinion this is not really a "fair" solution, as it changes the signature of the function:
- The caller must pass a double pointer instead of single pointer, and
- The caller should not use the returned pointer to assign it back to their own list pointer. Instead, the function returns a pointer to the successor node, which in most cases is not interesting information, so the caller could just ignore the return value.
The original function would have to be called like this:
head = delete_from_list(head, n);
...while this function should be called like this:
delete_from_list(&head, n);
For the above reasons, I am not sure this was the intended solution for the exercise. For other ideas to solve this, see this Q&A.