leetcode题解-链表排序算法 147. Insertion Sort List && 148 . Sort List

本文介绍了链表的插入排序、归并排序和快速排序算法,并通过具体题目实例展示了各种排序方法的实现过程及其优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇博客中我们回顾了数组的几大常用排序算法,这里结合链表的两道题目,我们将会实现链表的插入排序、归并排序和快速排序。首先看一下147插入排序这道题目,插入排序,顾名思义就是挨个遍历链表中的元素,然后将其插入到前面已经排序好的链表的相应位置,保持前面的链表仍然是排序链表即可。那么按照这个思路我们可以写出下面的代码:

    public static ListNode insertionSortList(ListNode head){
        if(head == null)
            return head;
        //定义一个新的链表用于存储排序好的链表
        ListNode newHead = new ListNode(0);
        //cur用于遍历要排序的链表,pre指向排序号的链表,next暂存剩下的链表
        ListNode cur=head, pre=newHead, next;
        while(cur != null){
            //保存cur后面的链表
            next = cur.next;
            //一=般都选择在当前元素后面插入,因为这样比较方便,也不会出现边界异常的现象
            while(pre.next != null && pre.next.val < cur.val)
                pre = pre.next;
            //插入到相应位置
            cur.next = pre.next;
            pre.next = cur;
            //恢复pre和cur的值
            pre = newHead;
            cur = next;
        }
        return newHead.next;
    }

上面这种方法简单直接,但是由于每次插入的时候都要从第一个元素开始遍历,所以会导致算法的效率比较低,那么应该如何改进呢?我们可以考虑一下,在上一次插入之后不把pre重定向到newHead,而是就留在刚才插入的位置,这样当下一个元素到来时直接从该位置开始遍历,如果pre的值比较大,那么久返回newHead重新遍历,如果pre的值小于cur,那就直接向后遍历,这样就省去了从头到pre的遍历时间,可以大大的提升代码效率。这样可以击败98%的用户,代码入下:

    public static ListNode insertionSortList2(ListNode head){
        if(head == null)
            return head;
        ListNode newHead = new ListNode(0);
        ListNode cur=head, pre=newHead, next;
        while(cur != null){
            next = cur.next;
            if(pre != newHead && pre.val > cur.val)
                pre = newHead;
            while(pre.next != null && pre.next.val < cur.val)
                pre = pre.next;
            cur.next = pre.next;
            pre.next = cur;
            cur = next;
        }
        return newHead.next;
    }

此外,还有一种思路就是针对插入位置是链表的头和尾时分别考虑,上面的方法使用新建一个newHead和pre.next.val的方法避免了这两种边界情况。那么我们也可以分别考虑然后进行插入操作,代码入下:

    public ListNode insertionSortList1(ListNode head) {
        if (head == null || head.next == null)
            return head;

        ListNode sortedHead = head, sortedTail = head;
        head = head.next;
        sortedHead.next = null;

        while (head != null)
        {
            ListNode temp = head;
            head = head.next;
            temp.next = null;
            // new val is less than the head, just insert in the front
            if (temp.val <= sortedHead.val)
            {
                temp.next = sortedHead;
                sortedTail = sortedHead.next == null ? sortedHead : sortedTail;
                sortedHead = temp;
            }
            // new val is greater than the tail, just insert at the back
            else if (temp.val >= sortedTail.val)
            {
                sortedTail.next = temp;
                sortedTail = sortedTail.next;
            }
            // new val is somewhere in the middle, we will have to find its proper
            // location.
            else
            {
                ListNode current = sortedHead;
                while (current.next != null && current.next.val < temp.val)
                {
                    current = current.next;
                }

                temp.next = current.next;
                current.next = temp;
            }
        }

        return sortedHead;
    }

上面就是链表直接插入排序法,还是比较简单的,那么接下来让我们看看148题,也就是使用归并排序和快速排序的方法实现链表排序吧。
首先看一下归并排序,其思路是先将所有元素两两划分为一组,然后递归合并,合并就相当于将两个排序好的链表合并为一个。这样最终我们就可以将链表排序好了。那么关键所在就是先分裂,在合并。代码如下所示:

    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null)
            return head;

        // 使用两个指针,一快一慢,当快指针走到链表尾部时,慢指针正好到中间位置。
        ListNode prev = null, slow = head, fast = head;

        while (fast != null && fast.next != null) {
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        //prev表示第一个链表的尾部,所以指向空。slow为第二个指针的头部
        prev.next = null;

        // 继续分裂
        ListNode l1 = sortList(head);
        ListNode l2 = sortList(slow);

        // 当分裂到最后每个元素时,开始两两合并
        return merge(l1, l2);
    }

    //将两个有序链表合并的函数
    ListNode merge(ListNode l1, ListNode l2) {
        ListNode l = new ListNode(0), p = l;

        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }

        if (l1 != null)
            p.next = l1;

        if (l2 != null)
            p.next = l2;

        return l.next;
    }

另外一种方法就是快速排序法,快速排序的思路就是先将链表根据某个基础元素值分为两部分,比他大的和比他小的,然后一直这样分类下去,最后在进行合并即可,代码如下所示:

ListNode sortList(final ListNode h){
    if(h == null || h.next == null)
        return h;

    /*split into three list*/
    ListNode fakesmall = new ListNode(0), small = fakesmall;
    ListNode fakelarge = new ListNode(0), large = fakelarge;
    ListNode fakeequal = new ListNode(0), equal = fakeequal;

    ListNode cur = h; // pivot is h.
    while(cur != null){
        if(cur.val < h.val){
            small.next = cur;
            small = small.next;
        }
        else if(cur.val == h.val){
            equal.next = cur;
            equal = equal.next;
        }
        else{
            large.next = cur;
            large = large.next;
        }

        cur = cur.next;
    }

    // put an end.
    small.next = equal.next = large.next = null;
    // merge them and return . merge reusing below one. merge for quicksort should be simplified. 
    return merge(merge(sortList(fakesmall.next), sortList(fakelarge.next)),fakeequal.next) ;
}

    ListNode merge(ListNode l1, ListNode l2) {
        ListNode l = new ListNode(0), p = l;

        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }

        if (l1 != null)
            p.next = l1;

        if (l2 != null)
            p.next = l2;

        return l.next;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值