zoukankan      html  css  js  c++  java
  • LootCode-链表排序-Java

    链表排序

    0.来源

    来源:力扣(LeetCode)
    题目链接:https://leetcode-cn.com/problems/sort-list

    1.题目描述

    在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

    2.测试用例

    示例 1:
    
    输入: 4->2->1->3
    
    输出: 1->2->3->4
    
    示例 2:
    
    输入: -1->5->3->4->0
    
    输出: -1->0->3->4->5
    

    3.解题思路

    3.1 总体思路

    ​ 看到链表排序,给我的第一个反应就是应该是能实现,主要是我对这题有解题的思路,先不说时间复杂度和空间复杂度什么的,我感觉选择排序或者是插入排序应该都能实现对链表的排序

    ​ Talk is cheap show me the code..

    好吧,上伪代码.(由于我主要用的是Java编程,所以就用Java 来实现了)

    while(没有到最后一个节点){
      Node cursorNode = currentNode.next;
      
      while( cursorNode != null){
        
          把找到比第一层循环节点的小的节点与它进行交换
            
          cursorNode = cursorNode.next;
      }
      
    }
    

    大概就是这样 ,和 选择排序实现差不多。

    但是看题目: 需要时间复杂度 O(n log n) 还有 常数级别的空间复杂度,这个需要的时间复杂度,让我想起了归并排序,一看是也是没有想通,但是看了遍数组的归并排序和LeetCode上大佬们的题解就清晰思路了,下面是归并排序的基本思路

    3.2归并排序思路说明

    3.2.1 基本思想

    总体概括就是从上到下递归拆分,然后从下到上逐步合并。

    • 递归拆分

    先把待排序数组分为左右两个子序列,再分别将左右两个子序列拆分为四个子子序列,以此类推直到最小的子序列元素的个数为两个或者一个为止。

    • 逐步合并

    将最底层的最左边的一个子序列排序,然后将从左到右第二个子序列进行排序,再将这两个排好序的子序列合并并排序,然后将最底层从左到右第三个子序列进行排序..... 合并完成之后记忆完成了对数组的排序操作(一定要注意是从下到上层级合并,可以理解为递归的层级返回)

    3.2.2 算法步骤

    1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
    2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
    3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
    4. 重复步骤 3 直到某一指针达到序列尾;
    5. 将另一序列剩下的所有元素直接复制到合并序列尾。

    3.2.3 动态演示

    img

    3.2.4 算法特性

    和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

    3.2.5 代码展示

    /**
     * 递归拆分
     * @param arr   待拆分数组
     * @param left  待拆分数组最小下标
     * @param right 待拆分数组最大下标
     */
    public static void mergeSort(int[] arr, int left, int right) {
        int mid = (left + right) / 2;  // 中间下标
        if (left < right) {
            mergeSort(arr, left, mid); // 递归拆分左边
            mergeSort(arr, mid + 1, right); // 递归拆分右边
            sort(arr, left, mid, right); // 合并左右
        }
    }
    
    /**
     * 合并两个有序子序列
     * @param arr   待合并数组
     * @param left  待合并数组最小下标
     * @param mid   待合并数组中间下标
     * @param right 待合并数组最大下标
     */
    public static void sort(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1]; // 临时数组,用来保存每次合并年之后的结果
        int i = left;
        int j = mid + 1;
        int k = 0; // 临时数组的初始下标
        // 这个while循环能够初步筛选出待合并的了两个子序列中的较小数
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        // 将左边序列中剩余的数放入临时数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        // 将右边序列中剩余的数放入临时数组
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        // 将临时数组中的元素位置对应到真真实的数组中
        for (int m = 0; m < temp.length; m++) {
            arr[m + left] = temp[m];
        }
    }
    

    3.3链表使用归并排序注意点

    1.找到中间节点

    ​ 解: 这个方法是使用 【slow fast 快慢双指针】 来完成的,听起来是挺高大上的,其实原理特别简单,就是一个每次向后挪动一个、另一个向后挪动两个,肯定是快指针的先到最后,而且是慢指针的二倍。 这就和跑步一样,如果一个人的速度是你的二倍,在相同时间内,他的路程肯定是你的二倍。

    ​ 中间节点也根据节点个数来分开,如果是奇数个,中间节点就是中间,如果是偶数个中间节点就是中间位置的前一个节点 ,其实 把慢指针当作中间节点就可以了。

    2.从中间节点断开,然后分别用这两个链表进行排序

    ​ 如何断开: 就是将 slow指针的next节点用一个节点给保存下来当作右边链表的开始节点,并将slow指针的next设置成 null

    4.代码实现

    class ListNode {
        int val;
        ListNode next;
    
        ListNode(int x) {
            val = x;
        }
    }
    
    public class LinkListSort {
    
        public static ListNode sortList(ListNode head) {
    
            // 设置递归终止条件:如果是一个节点,或者是 null 就可以返回
            if ( head == null || head.next == null)
            {
                return head;
            }
    
            // 通过 快慢双指针 来寻找链表分割的点
            ListNode slowNode = head;
            ListNode fastNode = head.next;
    
            while (fastNode!=null && fastNode.next!=null)
            {
                slowNode = slowNode.next;
                fastNode = fastNode.next.next;
            }
    
            // 设置右部分链表的开始部分
            ListNode temp = slowNode.next;
    
            // 从中间断开链表
            slowNode.next = null;
    
            ListNode leftNode = sortList((ListNode) head);
    
            ListNode rightNode = sortList((ListNode) temp);
    
            //设置一个新的头节点来保存排序后的效果
            ListNode cursorNode = new ListNode(0);
            ListNode resNode = cursorNode;
    
            // 对两个链表进行排序
    
            while ( leftNode!=null && rightNode!=null)
            {
                if(leftNode.val < rightNode.val)
                {
                    cursorNode.next= leftNode;
                    leftNode = leftNode.next;
                }else{
                    cursorNode.next = rightNode;
                    rightNode = rightNode.next;
                }
    
                // 将指针节点向后移动
                cursorNode = cursorNode.next;
            }
    
            // 判断两条链表是否循环到结尾,如果没循环到结尾将未循环完的挂在上面
            cursorNode.next = leftNode == null ? rightNode : leftNode;
    
    
            return resNode.next;
        }
    
    
        public static void main(String[] args) {
            ListNode head = new ListNode(4);
            ListNode a = new ListNode(2);
            ListNode b = new ListNode(1);
            ListNode c = new ListNode(3);
    
            head.next =a;
            head.next.next = b;
            head.next.next.next=c;
    
            ListNode listNode = sortList2( head);
    
            while ( listNode!=null )
            {
                System.out.print(listNode.val+" ");
    
                listNode = listNode.next;
            }
        }
      
    }
    

    5.总结

    ​ 1. 学习到了slow 和 fast 双指针,
    2. 还有归并排序在指针上面使用的优点,不用在申请空间了,没有数组那么浪费空间,简直就是给链表量身定做的排序算法。

  • 相关阅读:
    TCP/IP——何时用UDP代替TCP
    网络编程——客户/服务器程序设计范式
    各种常见英特网应用协议的使用情况和传输层三大协议的异同
    linux dd使用记录
    杂谈-为什么我们不要去外包公司
    小雪的成都
    哪个的情感
    mac上的git completion
    ue4 UE4Editor.lib找不到
    ue4 重新生成ide project文件的命令行
  • 原文地址:https://www.cnblogs.com/kangxinxin/p/12348325.html
Copyright © 2011-2022 走看看