A Graph is a non-linear data structure consisting of nodes and edges. The nodes are sometimes also referred to as vertices and the edges are lines or arcs that connect any two nodes in the graph. More formally a Graph can be defined as
-
1_the function just returns crap or gives me an error_ It would help if you showed us these things, instead of basically just saying "bad stuff happened". – John Gordon May 22 '20 at 16:39
-
1I'm a little bit confused by your code. You are calculating the length of the list and also checking if you are at the halfway point at the same time? When would `ret_count` ever equal `(self.len-1)/2`? – Sri May 22 '20 at 16:40
-
1Please show some more code but from what I can see, when you do ``self.find_mid(node.next, ret_count + 1)`` you don't put the returned value somewhere and return it. – Shachaf Zohar May 22 '20 at 16:56
3 Answers
Recursion is a poor choice for operating on linked lists. Almost always use a loop, which is simple to reason about, has less overhead and doesn't limit the size of the list to the call stack. It's easier to access and manipulate surrounding elements iteratively.
Getting the midpoint of a linked list is easy iteratively: keep two references to the head, then move one twice as fast as the other until the fast reference reaches the end of the list. The slow pointer will point to the middle node. The two-pointer technique is one of the fundamental tools for working with linked lists.
from collections import namedtuple
def middle_node(fast):
slow = fast
while fast and fast.next:
fast = fast.next.next
slow = slow.next
return slow
if __name__ == "__main__":
Node = namedtuple("Node", "val next")
odd = Node(0, Node(1, Node(2, Node(3, Node(4, None)))))
even = Node(0, Node(1, Node(2, Node(3, Node(4, Node(5, None))))))
print(middle_node(odd).val) # => 2
print(middle_node(even).val) # => 3
You can do this recursively using the exact same methodology: a fast and a slow reference. Here's an example that plugs into the above boilerplate:
def middle_node(fast, slow=None):
if not slow:
slow = fast
if fast and fast.next:
return middle_node(fast.next.next, slow.next)
return slow
For odd-length lists with two middle elements, these methods always return the one closer to the tail, but you can add an additional fast.next.next
check to the base case or loop termination conditional to return the middle element closer to the head if you need.
You can see the recursive version offers no benefits in readability or elegance to justify the added overhead and size restrictions it imposes. Recursion is better suited for nonlinear nested structures like trees where the data is wide (meaning the call stack is much more able to hold the data without overflowing). The nonlinear nature of trees makes it very awkward to use a loop and explicit stack to handle certain typical traversals.

- 44,755
- 7
- 76
- 106
The reason why this prints the right value but changing the print to a return statement doesn't work is because you don't return the node in your base case. So when you find the mid point and return the node, the previous node does not return anything or utilize the result of the recursive step. Here is a modification that will utilize the result from your recursive step and return it down the call chain.
I'm not entirely convinced you midpoint calculation was correct in every case (case of 3 nodes will return node 1 in stead of node 2) so I have modified it slightly.
def find_mid(self, node, ret_count, ):
if node.next == None:
self.len += 1
return None
else:
self.len += 1
# return_node will either be the midpoint if we have found it, or None if we are still searching
return_node = self.find_mid(node.next, ret_count + 1)
# We have found the midpoint no need to check ret_count anymore
if return_node:
return return_node
# If we make it here we have not found the midpoint node but have counted the total number of Nodes.
# Set midpoint assuming an even number of nodes
midpoint = int(self.len/2)
# If odd number of nodes set midpoint accordingly
if self.len % 2 != 0:
midpoint = int((self.len+1)/2)
# Check if the current node is the midpoint (len = 3 or len = 4 results in node 2 being midpoint
if ret_count == midpoint:
# Return the node
return node
else:
# Potentially not necessary but will ensure that non-midpoint recursive steps return None
return None

- 652
- 9
- 21
Recursion is a great choice for processing linked lists because linked lists are recursive data structures. Recursion allows our program's structure to match that of our data's structure -
# linked_list.py
empty = None
class node:
def __init__(self, value, next = None):
self.value = value
self.next = next
def to_str(t = empty):
if not t:
return "None"
else:
return f"{t.value} -> {to_str(t.next)}"
def middle(t = empty):
def loop(t, ff):
if not t:
return None
elif ff and ff.next:
return loop(t.next, ff.next.next)
else:
return t.value
return loop(t, t)
Let's get some output from our basic building blocks -
# main.py
from linked_list import node, to_str, middle
t1 = node(1, node(2, node(3)))
t2 = node(1, node(2, node(3, node(4))))
t3 = node(1, node(2, node(3, node(4, node(5)))))
print(to_str(t1)) # 1 -> 2 -> 3 -> None
print(to_str(t2)) # 1 -> 2 -> 3 -> 4 -> None
print(to_str(t3)) # 1 -> 2 -> 3 -> 4 -> 5 -> None
print(middle(t1)) # => 2
print(middle(t2)) # => 3
print(middle(t3)) # => 3
Wrapping up the functional module in a class
makes it feel more pythonic -
# linked_list.py
empty = # ...
class node # ...
def to_str # ...
def middle # ...
def from_list(l = []):
if not l:
return empty
else:
return node(l[0], from_list(l[1:]))
class linked_list:
def __init__(self, root = None):
self.root = root
def __str__(self):
return to_str(self.root)
def middle(self):
return middle(self.root)
def from_list(l = []):
return linked_list(from_list(l))
Now we get all the benefits of a functional-style module with the conveniences of an oop-style interface -
from linked_list import linked_list
t1 = linked_list.from_list([1, 2, 3])
t2 = linked_list.from_list([1, 2, 3, 4])
t3 = linked_list.from_list([1, 2, 3, 4, 5])
print(t1) # 1 -> 2 -> 3 -> None
print(t2) # 1 -> 2 -> 3 -> 4 -> None
print(t3) # 1 -> 2 -> 3 -> 4 -> 5 -> None
print(t1.middle()) # => 2
print(t2.middle()) # => 3
print(t3.middle()) # => 3

- 129,518
- 31
- 228
- 259