One way would be to put the notes in a new pile, represented by a linked list (a simple array is also possible). You'd insert the notes into the pile one by one, making sure each note goes in the appropriate place and does not upset the pre-existing order. What's the overhead for doing things this way? Well, you have to search the pile and insert the new note, which takes O(n) if we don't do anything clever. Since you'll be doing that for all n items, the total cost of sorting the notes is O(n^2), which isn't great. In the word of programming, this is known as an insertion sort. You can improve each insertion time by using a binary search tree to represent the pile. This is known as a tree sort, and decreases the search and total sort time to O(\log(n)) and O(n \log(n)), respectively. An auto-balancing BST works best, since in that case even the worst sort time is O(n \log(n)).
If you want O(n \log(n)) sort performance, but don't feel like mentally implementing an auto-balancing BST to sort a couple of notes, there are some alternatives. My personal favorite is merge sort, which, incidentally, is this week's topic. Merge sort is a divide-and-conquer algorithm that is best defined recursively: divide the items into two equally-sized piles, recursively sort each pile, then merge the sorted piles back together. The recursion terminates when a pile cannot be divided any further (i.e. contains less than two elements). What's the computational complexity of merge sort? Since you halve the input array at each recursion depth, the total depth of recursion is \lceil \log_2(n) \rceil. At each recursion depth, you also have to merge two sorted piles -- this can be done in linear time. The overall sort thus takes O(n \log(n)) (since we don't care about the base of the logarithm).
This week's problem was to implement the merge step of the merge sort. To make things more interesting, we're merging an arbitrary number of piles of arbitrary size, instead of two piles of roughly the same size. Here it is, in Python (for a change):
Since I just couldn't help myself, I went ahead and implemented the entire merge sort in Python. It doesn't share any code with the merge step mentioned above, but the principles are the same.
This week's problem was to implement the merge step of the merge sort. To make things more interesting, we're merging an arbitrary number of piles of arbitrary size, instead of two piles of roughly the same size. Here it is, in Python (for a change):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# lessons learnt: | |
# | |
# - python's list has no find() method -- the correct name is index() | |
# - next() is a built-in Python function -- http://stackoverflow.com/questions/1733004/python-next-function | |
# | |
def merge(arrays): | |
result = list() | |
while arrays: | |
heads = [a[0] for a in arrays] | |
next_value = min(heads) | |
arrays[heads.index(next_value)].remove(next_value) | |
result.append(next_value) | |
arrays = filter(None, arrays) | |
return result | |
if __name__ == "__main__": | |
arrays = [[0, 5, 8, 9], [1, 2, 7], [10]] | |
print merge(arrays) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def mergesort(array, start, end): | |
if end - start < 2: | |
# | |
# Do nothing. | |
# | |
return | |
half = (end-start)/2 | |
# | |
# Divide and conquer step. | |
# | |
mergesort(array, start, start+half) | |
mergesort(array, start+half, end) | |
# | |
# Merge using temporary storage. | |
# | |
tmp = list() | |
left = start | |
right = start+half | |
for i in range(end-start): | |
if left == start+half: | |
assert right < end | |
advance_left = False | |
elif right == end: | |
assert left < start+half | |
advance_left = True | |
else: | |
advance_left = array[left] < array[right] | |
if advance_left: | |
tmp.append(array[left]) | |
left += 1 | |
else: | |
tmp.append(array[right]) | |
right += 1 | |
# | |
# Copy temporary storage back into input array. | |
# | |
for i,val in enumerate(tmp): | |
array[start+i] = val | |
if __name__ == "__main__": | |
array = range(100) | |
import random | |
random.shuffle(array) | |
mergesort(array, 0, len(array)) | |
print array == sorted(array) |