链接列表的归并排序

0 投票
3 回答
620 浏览
提问于 2025-04-18 18:30

我在使用归并排序对链表进行排序时遇到了一些麻烦。我发现问题出在我的分区方法上。

每当我进行分区并请求前半部分时,另一半却和之前的部分完全一样。但是如果我不请求前半部分,后半部分就能正确返回。下面是我的代码:

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)

3 个回答

1

首先,在你代码的前半部分,对于第二个元素,你调用了

first_half = first_half_pointer
first_half.next = None

然后

first_half_pointer = first_half_pointer.next

所以 first_half_pointer 变成了 None,循环结束了,这样第一半部分的元素最多只有两个。

其次,更重要的是,你通过这一行代码打断了原始列表的连接:

first_half.next = None

所以在执行 partition(root, True) 之后,列表的后半部分就丢失了。

1

你想要的分区是破坏性的还是非破坏性的?也就是说,运行分区后,原来的列表还会保留所有的值吗?你可以选择任何一种方式来分区。

破坏性分区可能会稍微快一点,因为它只需要断开一个节点的连接(不过找到那个节点的时间是O(N))。如果你想要非破坏性地获取列表的前半部分,你需要复制它(为每个值创建新的节点)。这同样是O(N),但可能因为内存分配的原因,实际的时间会更长。

这里有一个简单的破坏性分区示例。它使用了不同的初始值,所以我们可以直接在slow_pointer之后进行分割,而不是在之前(那样会更复杂)。而且,分区时一次只获取一个半部分其实没有太大意义,因为如果你请求了前半部分,后半部分就无法再访问了,所以我们总是返回两个半部分。

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

这里有一个非破坏性的版本。列表的后半部分仍然引用原来的节点,但返回的前半部分是新节点的复制。

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

这两个函数都能正确处理任何长度的列表,包括长度为一(ListNode("foo"))和长度为零(None)。对于奇数长度的列表,返回的前半部分总是会比后半部分大。

1

你的 merge 方法似乎有个问题,它会把列表中的一些元素丢掉。想想下面这行代码会发生什么:

      new_list.next = None

这样做实际上是把剩下的列表给丢掉了。看起来每次合并时,你只会保留整体中最小的元素,以及每个列表中的最小元素,总共只保留三个元素。试试下面的 main 方法,看看我说的是什么意思:

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)

撰写回答