n log n is the win
Splitting halves the row each level, and there are about log levels. Each level costs n, so the total is n log n, which for a million items is far under a million squared.
Merge sort splits the row in half, sorts each half, then merges the two ordered halves. Splitting means the work is n log n, far better than n squared.
About 3 min of reading
Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.
Run merge sort and its merge step.
Merge two ordered rows.
Before you run it, say out loud what it will print. Then open the answer.
Sort a row with merge sort.
Before you run it, say out loud what it will print. Then open the answer.
Show the split of four.
Before you run it, say out loud what it will print. Then open the answer.
Count the split depth of eight.
Before you run it, say out loud what it will print. Then open the answer.
Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.
Splitting halves the row each level, and there are about log levels. Each level costs n, so the total is n log n, which for a million items is far under a million squared.
Merge sort is fast and stable but needs room for the merged rows. When memory is tight, an in-place sort is chosen instead.
nums = [5, 2, 8, 1]
mid = len(nums) // 2
print(nums[:mid], nums[mid:])
The row splits into two halves. Each half is sorted on its own, then the two are joined.
left = [2, 5]
right = [1, 8]
out = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
out.append(left[i]); i += 1
else:
out.append(right[j]); j += 1
out += left[i:]
out += right[j:]
print(out)
Take the smaller front each time. Two ordered rows merge into one ordered row in one pass.
def msort(a):
if len(a) <= 1:
return a
mid = len(a) // 2
L = msort(a[:mid])
R = msort(a[mid:])
out = []
i = j = 0
while i < len(L) and j < len(R):
if L[i] <= R[j]:
out.append(L[i]); i += 1
else:
out.append(R[j]); j += 1
return out + L[i:] + R[j:]
print(msort([5, 2, 8, 1]))
Sort each half, then merge. The <= keeps equal items in order, so it is stable.
print([7])
A row of one is already sorted. That is where the splitting stops, the bottom of the tree.
print(len([1, 2, 3, 4, 5, 6, 7, 8]).bit_length())
Eight items split three times to reach singles. The number of splits grows slowly, which is the log part.
left = [1, 3]
right = [2, 4]
print(len(left) + len(right))
Merging touches each item once. Linear merge times log splits is n log n.
left = [[2, "a"]]
right = [[2, "b"]]
print(left[0] <= right[0])
Using <= takes the left item first on a tie. That keeps the halves in order, so the sort is stable.
nums = [3, 1]
print(sorted(nums))
Merge sort builds new rows while merging. It uses extra memory, unlike an in-place swap sort.
Each one is defined here on this site. Open the entry for the full definition and the other words it points at.