Skip to content

Instantly share code, notes, and snippets.

@jakerockland
Forked from zed/mergesort-linkedlist.py
Created December 14, 2015 12:44
Show Gist options
  • Save jakerockland/e058ee754204b8455854 to your computer and use it in GitHub Desktop.
Save jakerockland/e058ee754204b8455854 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
"""Merge sort a singly linked linear list."""
import random
from itertools import product
# Linked list is either empty or a value and a link to the next list
empty = None # empty list
class LL(object):
__slots__ = "value", "next"
def __init__(self, value, next=empty):
self.value = value
self.next = next
def llistsort(head):
"""Translation from C to Python of listsort() [1].
O(N log N) (best & worst) -time, O(1)-space
inplace Mergesort algorithm for a singly linked linear list [2]
>>> head = mklist([2, 3, 1])
>>> tolist(head)
[2, 3, 1]
>>> tolist(llistsort(head))
[1, 2, 3]
[1]: http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c
[2]: http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
"""
insize = 1 # lists of size 1 are always sorted
while True: # merge adjacent pairs of `insize`-sized sorted lists
if __debug__: # check the loop invariant
pairwise = lambda L: zip(L, L[1:])
issorted = lambda L: all(x <= y for x, y in pairwise(L))
# all adjacent `insize`-sized lists are sorted
lst = tolist(head)
assert all(issorted(lst[i:i+insize])
for i in range(0, len(lst), insize))
p = head # head of the left list to be merged
head, tail = empty, empty # head and tail of the output list
nmerges = 0 # count number of merges we do in this pass
while p is not empty:
nmerges += 1 # there exists a merge to be done
# step `insize' places along from p or until the end of
# the list, whichever comes first
q = p # head of the right list to be merged
for psize in range(1, insize + 1):
q = q.next
if q is empty: # the end of the list (q is empty list)
break
qsize = insize
# merge a list starting at p, of length psize, with a list
# starting at q of length *at most* qsize
while psize > 0 or (qsize > 0 and q is not empty):
# decide whether next element of merge comes from p or q
if psize == 0: # p is empty
e, q = q, q.next # e must come from q, pop q
qsize -= 1
elif qsize == 0 or q is empty: # q is empty
e, p = p, p.next # e must come from p, pop p
psize -= 1
elif p.value <= q.value: # first element of p is lower or same
# choose p in the case of `p.value == q.value` to
# maintain sort stability
e, p = p, p.next # e must come from p, pop p
psize -= 1
else: # p.value > q.value i.e., first element of q is lower
e, q = q, q.next # e must come from q, pop q
qsize -= 1
# add e to the end of the output list
if tail is not empty:
tail.next = e
else: # 1st iteration
head = e # smallest item among two lists
tail = e
# now p has stepped `insize' places along, and q has too (or eol)
p = q # move to the next pair of lists to merge
#end of while p is not empty:
if tail is not empty:
tail.next = empty # terminate the output list
# if we have done only one merge, we're finished
if nmerges <= 1: # allow for nmerges==0, the empty list case
return head
else:# otherwise repeat, merging lists twice the size
insize *= 2
def mklist(initializer):
it = reversed(initializer)
try:
x = next(it)
except StopIteration:
return empty # empty list
else:
head = LL(x)
for value in it:
head = LL(value, head)
return head
def walk(head):
while head is not empty:
yield head.value
head = head.next
def tolist(head):
return list(walk(head))
def test():
for n, r, k in product(range(10), repeat=3):
L = list(range(n)) + [n//2]*r + [n-1]*k
random.shuffle(L)
head = mklist(L)
assert tolist(head) == L
head = llistsort(head)
assert tolist(head) == sorted(L)
if __name__ == "__main__":
import doctest; doctest.testmod()
test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment