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.
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:
and if it didn't have a base case:
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:
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.
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...
(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.
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.
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.
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.
July 14 2013: Released Article
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.
Article Update Log
July 14 2013: Released Article