排序实现题目:对链表进行插入排序

题目

标题和出处

标题:对链表进行插入排序

出处:147. 对链表进行插入排序

难度

5 级

题目描述

要求

给定链表的头结点 head \texttt{head} head,使用插入排序对链表进行升序排序,并返回排序后的链表的头结点。

插入排序算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

下面是插入排序算法的一个动图示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

示例 0

示例

示例 1:

示例 1

输入: head   =   [4,2,1,3] \texttt{head = [4,2,1,3]} head = [4,2,1,3]
输出: [1,2,3,4] \texttt{[1,2,3,4]} [1,2,3,4]

示例 2:

示例 2

输入: head   =   [-1,5,3,4,0] \texttt{head = [-1,5,3,4,0]} head = [-1,5,3,4,0]
输出: [-1,0,3,4,5] \texttt{[-1,0,3,4,5]} [-1,0,3,4,5]

数据范围

  • 链表中结点的数目范围是 [1,   5000] \texttt{[1, 5000]} [1, 5000]
  • -5000 ≤ Node.val ≤ 5000 \texttt{-5000} \le \texttt{Node.val} \le \texttt{5000} -5000Node.val5000

解法

思路和算法

对链表插入排序,需要维护一个已排序的子链表,排序过程中将未排序的结点插入到已排序的子链表中的合适位置。由于待插入的结点值可能小于已排序的子链表中的最小结点值,为了方便插入操作,需要创建哑结点,将链表的头结点 head \textit{head} head 作为哑结点的后一个结点,则每次插入结点的位置一定在哑结点之后。

end \textit{end} end 表示已排序的子链表的最后一个结点。初始时 end = head \textit{end} = \textit{head} end=head,表示已排序的子链表只有头结点。当 end \textit{end} end 变成链表的最后一个结点时,所有结点都在已排序的子链表中,整个链表的排序结束。

end \textit{end} end 不是链表的最后一个结点时,链表中有尚未排序的结点,下一个待排序的结点是 end . next \textit{end}.\textit{next} end.next。比较 end \textit{end} end 的结点值与 end . next \textit{end}.\textit{next} end.next 的结点值,执行以下操作。

  • 如果 end \textit{end} end 的结点值小于等于 end . next \textit{end}.\textit{next} end.next 的结点值,则 end . next \textit{end}.\textit{next} end.next 应该插入到 end \textit{end} end 的后面,此时这两个结点之间的相对顺序已经正确,因此将 end \textit{end} end 移动到 end . next \textit{end}.\textit{next} end.next

  • 如果 end \textit{end} end 的结点值大于 end . next \textit{end}.\textit{next} end.next 的结点值,则 end . next \textit{end}.\textit{next} end.next 应该插入到已排序的子链表中。用 next \textit{next} next 表示 end . next \textit{end}.\textit{next} end.next,从哑结点开始遍历链表,找到值小于等于 next \textit{next} next 的结点值的最后一个结点 prev \textit{prev} prev,满足 prev \textit{prev} prev 的结点值小于等于 next \textit{next} next 的结点值且 prev . next \textit{prev}.\textit{next} prev.next 的结点值大于 next \textit{next} next 的结点值,将 next \textit{next} next 插入到 prev \textit{prev} prev 的后面。具体插入操作如下。

    1. end . next \textit{end}.\textit{next} end.next 指向 next . next \textit{next}.\textit{next} next.next
    2. next . next \textit{next}.\textit{next} next.next 指向 prev . next \textit{prev}.\textit{next} prev.next
    3. prev . next \textit{prev}.\textit{next} prev.next 指向 next \textit{next} next

当整个链表的排序结束之后,返回哑结点的后一个结点,即排序后的链表的头结点。

假设待插入结点的结点值是 val \textit{val} val。由于插入结点时都是寻找值小于等于 val \textit{val} val 的最后一个结点,如果已排序的子链表中存在值等于 val \textit{val} val 的结点,则待插入结点的插入位置一定在值等于 val \textit{val} val 的结点右侧,因此链表的插入排序算法是稳定的。

代码

class Solution {
    public ListNode insertionSortList(ListNode head) {
        ListNode dummyHead = new ListNode(0, head);
        ListNode end = head;
        while (end.next != null) {
            if (end.val <= end.next.val) {
                end = end.next;
            } else {
                ListNode prev = dummyHead, next = end.next;
                while (prev.next.val <= next.val) {
                    prev = prev.next;
                }
                end.next = next.next;
                next.next = prev.next;
                prev.next = next;
            }
        }
        return dummyHead.next;
    }
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是链表的长度。插入排序需要遍历链表一次,对于每个结点最多需要 O ( n ) O(n) O(n) 的时间遍历链表寻找插入位置并插入结点,因此链表插入排序的时间复杂度是 O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值