Advanced Python Part Two: Utilizing Recursion
Python is surprisingly useful for recursive algorithms, which allow for easy O(n log n) or O(log n) solutions to problems which would otherwise be very slow.
Python has a surprisingly great ability to perform large recursive operations, allowing for algorithms to be faster and easier to implement. In this article, I discuss the basics of recursion in python and applying divide and conquer to the sorting problem.
The Basics of Recursion
Before I start on advanced recurion topics, I'm going to discuss the basics of programming recursive functions. First, there is the base case. A base case is how your function stops recursively. An example of a recursive function (which lists all the numbers between one and one-hundred) with a base case:
def count(basenumber):
if basenumber >= 100:
return print int(basenumber)
count(basenumber + 1)
count(1) and if it didn't have a base case:
def count(basenumber):
print basenumber
count(basenumber)
count(1) The first piece of code prints out all the integers between the number you specify and one-hundred. The second doesn't have the Base Case which terminates the function eventually, making it run forever. To be more clear, this was the base case: if basenumber >= 100: return This makes sure that the function stops at one-hundred. It is important to establish your base case when you are programming a recursive application. This section should (hopefully) explain some recursion basics.
Using "Divide and Conquer" in Recursion
Divide and Conquer is an algorithm design paradigm which easily reduces problems into O(n log n) or O(log n) running time. If you don't know what these are, just know that they are very fast and often better than how fast a "brute-force" algorithm would be. Divide and Conquer means to divide your problem into a large amount of smaller sub-problems, and then solve the original problem using the solutions to these smaller problems.
This often relies on recursion. As an example of Divide and Conquer, there is Merge Sort. If you were to try the "brute-force" solution to sorting numbers (called selection-sort), it would be an O(n^2) algorithm (or quadratic). This is considered slow compared to how fast we can get with divide and conquer (merge sort). Using merge sort, we can get O(n log n). Selection Sort must loop over the list of numbers to sort for every number in the list...
def selectionSort(a):
# Go through all positions except the last one
# (that one will automatically be correct)
for index in range(len(a)-1):
value = a[index]
# enumerate all (index, value) pairs from the rest of the list
# and take the pair with the smallest value
min_subindex, min_value = min(enumerate(a[index+1:]), key=lambda x: x[1])
if min_value < value:
a[index] = min_value
a[min_subindex + index + 1] = value (That was fairly advanced selection sort code, and it can be confusing) ...while merge sort recursively calls itself, dividing the problem until there is a bunch of lists with only two numbers, and then sorting those lists. It then merges these lists in linear time.
def mergesort(numbers):
if len(numbers) == 1:
return numbers
elif len(numbers) == 2:
if numbers[0] < numbers[1]:
return numbers
else:
return [numbers[1], numbers[0]]
else:
length = len(numbers)
if length % 2 == 0:
list_one = mergesort(numbers[:(length / 2)])
list_two = mergesort(numbers[(length / 2):])
else:
list_one = mergesort(numbers[:(length - 1)])
list_two = mergesort(numbers[(length - 1):])
result = []
one_traversal = 0
two_traversal = 0
for traversal in range(len(numbers)):
if one_traversal == len(list_one) and two_traversal == len(list_two):
break
elif one_traversal == len(list_one):
result.append(list_two[two_traversal])
two_traversal += 1
elif two_traversal == len(list_two):
result.append(list_one[one_traversal])
one_traversal += 1
else:
if list_one[one_traversal] > list_two[two_traversal]:
result.append(list_two[two_traversal])
two_traversal += 1
else:
result.append(list_one[one_traversal])
one_traversal += 1
return result Look at the two base cases at the top. Then look at the merging routine. Merge sort can be confusing, and I recommend you read up on it more.
Interesting Points
Hopefully you understand Python recursion with Divide and Conquer more. I tried to keep the article brief and it may be confusing, so I recommend you ask questions below.
Conclusion
This was a brief introduction to Python algorithms with Divide and Conquer. Try implementing Strassens Matrix Multiplication algorithm (look it up) or an algorithm for counting inversions if you would like to learn more about the divide and conquer paradigm.
Related Tutorials
Balancing Game Development and Creative Direction in Indie Production
A practical look at how indie developers can balance creative direction with hands-on game development. This article co…
My Unreal Engine Development Process: From Core Idea to Playable Build
A practical overview of my Unreal Engine development process, covering how I move from a core game idea to a playable b…
Introducing LaneGraph: The Ultimate Road Network Solution for Unity
Discover the power of LaneGraph, a lightweight and flexible lane-based navigation system for Unity. LaneGraph makes it…
Retargeting Mixamo Characters with Root Motion In Unreal Engine 5.4.
I have always found Retargeting Mixamo Characters To have Root Motion is a serious lengthy Tast, Recently I stumbled up…
How To Make A SIMPLE Main Menu In Unity
In this tutorial for unity, i go over how to make a simple main menu for unity, it's an unlisted video because i do not…
Guide to Gameplay Balance
A perspective on competitive gameplay balance, from a background of "shooter" sandbox design.
Discussion
More from Thomas Hobohm
Pre-Visualization Is Important!
Many a beginner on gamedev.net (including me) has trouble with their software in the beginning. I had no idea how to pl…
Advanced Python Part One: List Comprehensions
Python List Comprehensions are a small tool with a large use case. They get rid of the tedious loops required for tasks…
Discussion