Insertion Sort List

Question

  1. Sort a linked list using insertion sort.
  2. Example
  3. Given 1->3->2->0->null, return 0->1->2->3->null.

题解1 - 从首到尾遍历

插入排序常见的实现是针对数组的,如前几章总的的 Insertion Sort,但这道题中的排序的数据结构为单向链表,故无法再从后往前遍历比较值的大小了。好在天无绝人之路,我们还可以从前往后依次遍历比较和交换。

由于排序后头节点不一定,故需要引入 dummy 大法,并以此节点的next作为最后返回结果的头节点,返回的链表从dummy->next这里开始构建。首先我们每次都从dummy->next开始遍历,依次和上一轮处理到的节点的值进行比较,直至找到不小于上一轮节点值的节点为止,随后将上一轮节点插入到当前遍历的节点之前,依此类推。文字描述起来可能比较模糊,大家可以结合以下的代码在纸上分析下。

Python

  1. """
  2. Definition of ListNode
  3. class ListNode(object):
  4. def __init__(self, val, next=None):
  5. self.val = val
  6. self.next = next
  7. """
  8. class Solution:
  9. """
  10. @param head: The first node of linked list.
  11. @return: The head of linked list.
  12. """
  13. def insertionSortList(self, head):
  14. dummy = ListNode(0)
  15. cur = head
  16. while cur is not None:
  17. pre = dummy
  18. while pre.next is not None and pre.next.val < cur.val:
  19. pre = pre.next
  20. temp = cur.next
  21. cur.next = pre.next
  22. pre.next = cur
  23. cur = temp
  24. return dummy.next

C++

  1. /**
  2. * Definition of ListNode
  3. * class ListNode {
  4. * public:
  5. * int val;
  6. * ListNode *next;
  7. * ListNode(int val) {
  8. * this->val = val;
  9. * this->next = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. public:
  15. /**
  16. * @param head: The first node of linked list.
  17. * @return: The head of linked list.
  18. */
  19. ListNode *insertionSortList(ListNode *head) {
  20. ListNode *dummy = new ListNode(0);
  21. ListNode *cur = head;
  22. while (cur != NULL) {
  23. ListNode *pre = dummy;
  24. while (pre->next != NULL && pre->next->val < cur->val) {
  25. pre = pre->next;
  26. }
  27. ListNode *temp = cur->next;
  28. cur->next = pre->next;
  29. pre->next = cur;
  30. cur = temp;
  31. }
  32. return dummy->next;
  33. }
  34. };

Java

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode(int x) { val = x; }
  7. * }
  8. */
  9. public class Solution {
  10. public ListNode insertionSortList(ListNode head) {
  11. ListNode dummy = new ListNode(0);
  12. ListNode cur = head;
  13. while (cur != null) {
  14. ListNode pre = dummy;
  15. while (pre.next != null && pre.next.val < cur.val) {
  16. pre = pre.next;
  17. }
  18. ListNode temp = cur.next;
  19. cur.next = pre.next;
  20. pre.next = cur;
  21. cur = temp;
  22. }
  23. return dummy.next;
  24. }
  25. }

源码分析

  1. 新建 dummy 节点,用以处理最终返回结果中头节点不定的情况。
  2. cur表示当前正在处理的节点,在从 dummy 开始遍历前保存cur的下一个节点作为下一轮的cur.
  3. pre作为遍历节点,直到找到不小于cur值的节点为止。
  4. pre的下一个节点pre->next链接到cur->next上,cur链接到pre->next, 最后将cur指向下一个节点。
  5. 返回dummy->next最为最终头节点。

Python 的实现在 lintcode 上会提示 TLE, leetcode 上勉强通过,这里需要注意的是采用if A is not None:的效率要比if A:高,不然 leetcode 上也过不了。具体原因可参考 Stack Overflow 上的讨论。

复杂度分析

最好情况:原链表已经有序,每得到一个新节点都需要 i 次比较和一次交换, 时间复杂度为 1/2O(n^2) + O(n), 使用了 dummy 和 pre, 空间复杂度近似为 O(1).

最坏情况:原链表正好逆序,由于是单向链表只能从前往后依次遍历,交换和比较次数均为 1/2 O(n^2), 总的时间复杂度近似为 O(n^2), 空间复杂度同上,近似为 O(1).

题解2 - 优化有序链表

从题解1的复杂度分析可以看出其在最好情况下时间复杂度都为 O(n^2) ,这显然是需要优化的。 仔细观察可发现最好情况下的比较次数 是可以优化到 O(n) 的。思路自然就是先判断链表是否有序,仅对降序的部分进行处理。优化之后的代码就没题解1那么容易写对了,建议画个图自行纸上分析下。

Python

  1. """
  2. Definition of ListNode
  3. class ListNode(object):
  4. def __init__(self, val, next=None):
  5. self.val = val
  6. self.next = next
  7. """
  8. class Solution:
  9. """
  10. @param head: The first node of linked list.
  11. @return: The head of linked list.
  12. """
  13. def insertionSortList(self, head):
  14. dummy = ListNode(0)
  15. dummy.next = head
  16. cur = head
  17. while cur is not None:
  18. if cur.next is not None and cur.next.val < cur.val:
  19. # find insert position for smaller(cur->next)
  20. pre = dummy
  21. while pre.next is not None and pre.next.val < cur.next.val:
  22. pre = pre.next
  23. # insert cur->next after pre
  24. temp = pre.next
  25. pre.next = cur.next
  26. cur.next = cur.next.next
  27. pre.next.next = temp
  28. else:
  29. cur = cur.next
  30. return dummy.next

C++

  1. /**
  2. * Definition of ListNode
  3. * class ListNode {
  4. * public:
  5. * int val;
  6. * ListNode *next;
  7. * ListNode(int val) {
  8. * this->val = val;
  9. * this->next = NULL;
  10. * }
  11. * }
  12. */
  13. class Solution {
  14. public:
  15. /**
  16. * @param head: The first node of linked list.
  17. * @return: The head of linked list.
  18. */
  19. ListNode *insertionSortList(ListNode *head) {
  20. ListNode *dummy = new ListNode(0);
  21. dummy->next = head;
  22. ListNode *cur = head;
  23. while (cur != NULL) {
  24. if (cur->next != NULL && cur->next->val < cur->val) {
  25. ListNode *pre = dummy;
  26. // find insert position for smaller(cur->next)
  27. while (pre->next != NULL && pre->next->val <= cur->next->val) {
  28. pre = pre->next;
  29. }
  30. // insert cur->next after pre
  31. ListNode *temp = pre->next;
  32. pre->next = cur->next;
  33. cur->next = cur->next->next;
  34. pre->next->next = temp;
  35. } else {
  36. cur = cur->next;
  37. }
  38. }
  39. return dummy->next;
  40. }
  41. };

Java

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode(int x) { val = x; }
  7. * }
  8. */
  9. public class Solution {
  10. public ListNode insertionSortList(ListNode head) {
  11. ListNode dummy = new ListNode(0);
  12. dummy.next = head;
  13. ListNode cur = head;
  14. while (cur != null) {
  15. if (cur.next != null && cur.next.val < cur.val) {
  16. // find insert position for smaller(cur->next)
  17. ListNode pre = dummy;
  18. while (pre.next != null && pre.next.val < cur.next.val) {
  19. pre = pre.next;
  20. }
  21. // insert cur->next after pre
  22. ListNode temp = pre.next;
  23. pre.next = cur.next;
  24. cur.next = cur.next.next;
  25. pre.next.next = temp;
  26. } else {
  27. cur = cur.next;
  28. }
  29. }
  30. return dummy.next;
  31. }
  32. }

源码分析

  1. 新建 dummy 节点并将其next 指向head
  2. 分情况讨论,仅需要处理逆序部分。
  3. 由于已经确认链表逆序,故仅需将较小值(cur->next而不是cur)的节点插入到链表的合适位置。
  4. cur->next插入到pre之后,这里需要四个步骤,需要特别小心!

Insertion Sort

如上图所示,将cur->next插入到pre节点后大致分为3个步骤。

复杂度分析

最好情况下时间复杂度降至 O(n), 其他同题解1.

Reference