I am understanding recursion and so I tried writing reverse a linked list program. I have written the below function but it says segmentation error (core dumped).
void reverse(){
if (head -> next == NULL){
return;
}
reverse (head -> next);
struct node *q = (struct node*) malloc (sizeof(struct node));
q = head -> next;
q -> next = head;
head -> next = NULL;
}
Please can someone guide me. Thank you.
Recursion is a way of thinking that's gained by practice. So congratulations on your attempt. It's not correct, but don't be discouraged.
Here are two ways to go about the problem.
Your goal is to subdivide it into a smaller version of itself plus a (hopefully easy and fast to compute) incremental step that takes a solution to the smaller version to a complete solution. This is the essence of recursive thinking.
First try: Think of the list as a head element plus the "rest of the list." I.e.,
where
h
is the head elementR
is the rest of the list, and dot.
is joining a new element to the list. Reversing this list consists of reversingR
, then appendingh
on the end:This is a recursive solution because we can call the reverse function recursively to solve the slightly smaller problem of reversing
R
, then add a little work to appendh
, and that gives us the complete solution.The problem with this formulation is that appending
h
is more expensive than you'd like. Since we have a singly linked list with only a head pointer, it's time consuming: traverse the whole list. But it will work fine. In C it would be:So how to get rid of the bad performance? It's frequently the case that different recursive formulations of a problem have different efficiencies. So some trial and error is often involved.
Next try: Think about the computation in terms of dividing the list into two sublists: L = H T, so rev(L) = rev(T) + rev(H). Here plus
+
is list concatenation. The key is that if I knowrev(H)
and want to add a new element at its head, the element to add is the first element in T. If this seems fuzzy, let H = [a, b, c] and T = [d, e]. Then if I already know rev(H) = [c, b, a] and want to prepend the next element at the head, I want d, which is the first element of T. In our little notation, you can write this observation just so:So this looks very good. In both cases (getting the head of T and moving it to the head of rev(H)), I'm only interested in the head of the list, which is very efficient to access.
Of course if T is empty, then rev(H) = rev(L). This is the answer!
Writing this as recursive procedure.
At the start, we don't know anything at all about rev(H), so T is the whole list:
The next thing to note is that this function is tail recursive: the recursive call is executed just before the function returns. This is good! It means we can easily rewrite it as a loop:
You can always do this transformation with tail-recursive calls. You should think hard about why this works.
Now the
goto
is easily rewritten as a while loop, and we can makerev_h
a local variable initialized toNULL
, since that's all the initial call does:An in-place linked list reverser that needs only a small constant amount of space!
And look! We never had to draw funny box and arrow diagrams or think about pointers. It "just happened" by careful reasoning about how to subdivide the problem into smaller instances of itself, the essence of recursion. It's also a nice way to see that loops are just a special kind of recursion. Cool, no?