Ali
Ali

Reputation: 5476

Merge sorting a linked list

I've been having trouble using merge sort on a linked list. The issue I've discovered was the problem within my partition method.

Whenever I partition and request the first half, the other half turned out the be the exact same of the previous one. But if I don't request the first half, the second half returns correctly. Here is my code below;

class ListNode:
  def __init__(self, x, next = None):
    self.val = x
    self.next = next

def print_list(root):
  while root:
    print root.val,
    root = root.next
  print ''

def partition(root, is_first_half):
  print_list(root)
  if not root.next:
    return root
  slow_pointer, fast_pointer = root, root.next
  while fast_pointer and fast_pointer.next != None:
    fast_pointer = fast_pointer.next.next
    slow_pointer = slow_pointer.next
  if not is_first_half:
    return slow_pointer
  first_half, first_half_head, first_half_pointer = None, None, root
  while first_half_pointer and first_half_pointer != slow_pointer:
    if not first_half_head:
      first_half = first_half_pointer
      first_half_head = first_half
    else:
      first_half.next = first_half_pointer
      first_half = first_half_pointer
      first_half.next = None
    first_half_pointer = first_half_pointer.next
  return first_half_head


def merge(list1, list2):
    new_list_head, new_list = None, None
    if not list1:
      return list2
    if not list2:
      return list1
    while list1 and list2:
      if list1.val < list2.val:
        if not new_list_head:
          new_list = list1
          new_list_head = new_list
        else:
          new_list.next = list1
          new_list = list1
          new_list.next = None
        list1 = list1.next
      else:
        if not new_list_head:
          new_list = list2
          new_list_head = new_list
        else:
          new_list.next = list2
          new_list = list2
          new_list.next = None
        list2 = list2.next
    if not list1:
      while list2:
        new_list.next = list2
        new_list = list2
        new_list.next = None
        list2 = list2.next
      return new_list_head
    while list1:
      new_list.next = list1
      new_list = list1
      new_list.next = None
      list1 = list1.next
    return new_list_head


def mergesort(root):
  if not root.next:
    return root
  list1 = mergesort(partition(root, True))
  list2 = mergesort(partition(root, False))
  return merge(list1, list2)


if __name__ == '__main__':
  a = ListNode(2, ListNode(4, ListNode(3, ListNode(1, ListNode(7)))))
  print_list(a)
  a = mergesort(a)
  print_list(a)

Upvotes: 0

Views: 632

Answers (3)

Blckknght
Blckknght

Reputation: 104722

Do you intend your partitioning to be destructive? That is, should the original list still exist with all its values after you run the partition? You can partition either way.

Destructive partitioning may be a little faster, since it just needs to unlink one node (though finding the right node is O(N)). To get the first half of the list non-destructively you'll need to copy it (creating new nodes for each value). This is also O(N), but probably with larger constant terms due to memory allocation.

Here's a quick destructive partition. It uses different initial values than your code, so we can split directly after the slow_pointer rather than before it (which is more difficult). It also doesn't really make sense to get only one half of a destructive partition at a time, since if you request the first half the second half will become unreachable afterwards, so we return both halves always.

def partition(root):
    fast_pointer = slow_pointer = ListNode(None, root)  # new node "before the root"
    while fast_pointer and fast_pointer.next:
        fast_pointer = fast_pointer.next.next
        slow_pointer = slow_pointer.next
    mid = slow_pointer.next
    slow_pointer.next = None                            # break one link in the list
    return root, mid                                    # return both halves of the list

Here's an alternative version that is non-destructive. The second half of the list is a reference to the same nodes as in the original, but the returned first half is a copy in new nodes

def partition_non_destructive(root):
    root = fast_pointer = slow_pointer = ListNode(None, root)  # root anchors the copy
    while fast_pointer and fast_pointer.next:
        fast_pointer = fast_pointer.next.next
        slow_pointer.next = ListNode(slow_pointer.next.value,  # copy the next node
                                     slow_pointer.next.next)
        slow_pointer = slow_pointer.next  # slow_pointer is always the latest copied node
    mid = slow_pointer.next
    slow_pointer.next = None                           # unlink copied half from the rest
    return root.next, mid                              # return both halves of the list

Both of these functions work correctly for lists of any length, including length one (ListNode("foo")) and zero (None). For odd length lists, the returned first half will always be larger than the second half.

Upvotes: 1

Anton Savin
Anton Savin

Reputation: 41301

First, in your code for the first half, for the second element you call

first_half = first_half_pointer
first_half.next = None

and then

first_half_pointer = first_half_pointer.next

So first_half_pointer becomes None and the loop finishes, so the first half always has no more than two elements.

Second, and more important, you break the original list connectivity by this line:

first_half.next = None

So after partition(root, True) the second half of the list is lost.

Upvotes: 1

jonderry
jonderry

Reputation: 23633

Your merge method seems to have a bug whereby it drops a bunch of elements from the list. Think about what happens in the following line(s) of code:

      new_list.next = None

You are effectively dropping the rest of the list. It seems you will keep the smallest element overall, plus the smallest element of each list for a total of three elements whenever you perform a merge. Try the following main method to see what I mean:

if __name__ == '__main__':
  a = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5)))))
  b = ListNode(6, ListNode(7, ListNode(8, ListNode(9, ListNode(10)))))
  print_list(a)
  print_list(b)
  #a = mergesort(a)
  c = merge(a, b)
  print_list(c)
  print_list(a)
  print_list(b)

Upvotes: 1

Related Questions