Sort List

本文介绍了一种在链表上实现归并排序的方法,该方法能在O(nlogn)的时间复杂度内完成排序,并且仅使用常数级别的额外空间。文章详细解释了为何归并排序在这种情况下比快速排序更合适,并提供了具体的实现代码。

https://oj.leetcode.com/problems/sort-list/

Sort a linked list in O(n log n) time using constant space complexity.

public ListNode sortList(ListNode head)


这一题给出的提示就是算法复杂度,nlogn。很长一段时间我认为nlogn去排序一个链表是不可能的,在某次面试imdb的时候面试官问我是否可以,我表示不可以,面试官表示真的不可以?我表示真的不行。面试官就不高兴了。。。


事实上如果是一个数组,nlogn的排序算法有两个,一个是归并排序,另一个是快排。归并排序是一个稳定的nlogn排序算法,但同时也需要额外的nlogn的空间去完成,因为归并排序,将两个排好序的子数组合并的这一步,是无论如何都没办法Inplace完成的。快排则是一个不稳定的nlogn算法,复杂度跟元素分布高度相关,胜在可以inplace,同时也有好几种优化的方式,最常见的就是在partitioning的过程里,pivot的选取从固定某个位置变成随机选取,这样做的目的在于将复杂度和元素分布的关联度切断。


那么这一题到底是快排还是归并排序,其实仔细分析下会发现LinkedList里,归并排序更好发挥,而快排由于pivot没法随机选取,限制较大,而且这是一个单向链表,也就是把pivot确定之后,以pivot为界分开两边这一步骤也会稍嫌麻烦。相反LinkedList给归并排序带来了inplace的可能。因为原本无法inplace的合并这一步,变成了可能。那么,下面就给出归并排序的代码:

    public ListNode sortList(ListNode head) {
        if(head == null)
            return null;
        ListNode end = head;
        while(end.next != null){
            end = end.next;
        }
        return mergeSort(head, end);
    }
    
    public ListNode merge(ListNode left, ListNode right){
        ListNode head = left.val > right.val ? right : left;
        if(left.val > right.val){
            right = right.next;
        }else
            left = left.next;
        ListNode tmp = head;
        while(left != null || right != null){
            if(left == null || (right != null && left.val > right.val)){
                tmp.next = right;
                right = right.next;
                tmp = tmp.next;
            }else{
                tmp.next = left;
                left = left.next;
                tmp = tmp.next;
            }
        }
        return head;
    }
    
    public ListNode mergeSort(ListNode head, ListNode end){
        if(head == end)
            return head;
        else{
            int count = 0;
            ListNode tmp = head;
            while(tmp != end){
                count++;
                tmp = tmp.next;
            }
            int mid = count / 2;
            ListNode mid_node = head;
            while(mid != 0){//找中间点,然后分割左右。原归并排序中左边的长度也是大于等于右边
                mid_node = mid_node.next;
                mid--;
            }
            ListNode next_head = mid_node.next;
            mid_node.next = null;//这一步很重要,分割左右,最终其实就是把一个链表在递归最底层变成了一个个独立的节点,再归并起来。
            ListNode left = mergeSort(head, mid_node);
            ListNode right = mergeSort(next_head, end);
            return merge(left, right);
        }


源码地址: https://pan.quark.cn/s/3916362e5d0a 在C#编程平台下,构建一个曲线编辑器是一项融合了图形用户界面(GUI)构建、数据管理及数学运算的应用开发任务。 接下来将系统性地介绍这个曲线编辑器开发过程中的核心知识点:1. **定制曲线面板展示数据曲线**: - 控件选用:在C#的Windows Forms或WPF框架中,有多种控件可用于曲线呈现,例如PictureBox或用户自定义的UserControl。 通过处理重绘事件,借助Graphics对象执行绘图动作,如运用DrawCurve方法。 - 数据图形化:通过线性或贝塞尔曲线连接数据点,以呈现数据演变态势。 这要求掌握直线与曲线的数学描述,例如两点间的直线公式、三次贝塞尔曲线等。 - 坐标系统与缩放比例:构建X轴和Y轴,设定坐标标记,并开发缩放功能,使用户可察看不同区间内的数据。 2. **在时间轴上配置多个关键帧数据**: - 时间轴构建:开发一个时间轴组件,显示时间单位刻度,并允许用户在特定时间点设置关键帧。 时间可表现为连续形式或离散形式,关键帧对应于时间轴上的标识。 - 关键帧维护:利用数据结构(例如List或Dictionary)保存关键帧,涵盖时间戳和关联值。 需考虑关键帧的添加、移除及调整位置功能。 3. **调整关键帧数据,通过插值方法获得曲线**: - 插值方法:依据关键帧信息,选用插值方法(如线性插值、样条插值,特别是Catmull-Rom样条)生成平滑曲线。 这涉及数学运算,确保曲线在关键帧之间无缝衔接。 - 即时反馈:在编辑关键帧时,即时刷新曲线显示,优化用户体验。 4. **曲线数据的输出**: - 文件类型:挑选适宜的文件格式存储数据,例如XML、JSON或...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值