Skip to main content
GameDev.net gamedev.net

PRO Tired of ads? Read GameDev.net ad-free and help keep the community independent with GameDev Pro — $3/month.

Advanced Python Part Two: Utilizing Recursion
Article

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.

Thomas Hobohm
Thomas Hobohm
July 14, 2014 6k views

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.

License: gdol

Related Tutorials

Discussion

Discussion

Loading comments...

More from Thomas Hobohm