链表排序

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 动态演示

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. 还有归并排序在指针上面使用的优点,不用在申请空间了,没有数组那么浪费空间,简直就是给链表量身定做的排序算法。

LootCode-链表排序-Java的更多相关文章

  1. 常见的链表排序(Java版)

    上篇博客中讲解了九大内部排序算法,部分算法还提供了代码实现,但是那些代码实现都是基于数组进行排序的,本篇博客就以链表排序实现几种常见的排序算法,以飨读者. 快速排序的链表实现 算法思想:对于一个链表, ...

  2. 148. Sort List (java 给单链表排序)

    题目:Sort a linked list in O(n log n) time using constant space complexity. 分析:给单链表排序,要求时间复杂度是O(nlogn) ...

  3. 算法是什么(二)手写个链表(java)

    算法是什么(二)手写个链表(java)   liuyuhang原创,未经允许禁止转载 目录 算法是什么(〇) 很多语言的API中都提供了链表实现,或者扩展库中实现了链表. 但是更多的情况下,Map(或 ...

  4. 希尔排序及希尔排序java代码

    原文链接:http://www.orlion.ga/193/ 由上图可看到希尔排序先约定一个间隔(图中是4),然后对0.4.8这个三个位置的数据进行插入排序,然后向右移一位对位置1.5.9进行插入排序 ...

  5. C语言 链表的使用(链表的增删查改,链表逆转,链表排序)

    //链表的使用 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include< ...

  6. c语言:链表排序, 链表反转

    下面将实现链表排序的排序和遍历显示功能: 所定义的链表结构如下: head -> p1 -> p2 ->p3 ->....->pn; head的本身不作为数据节点,hea ...

  7. 链表插入和删除,判断链表是否为空,求链表长度算法的,链表排序算法演示——C语言描述

    关于数据结构等的学习,以及学习算法的感想感悟,听了郝斌老师的数据结构课程,其中他也提到了学习数据结构的或者算法的一些个人见解,我觉的很好,对我的帮助也是很大,算法本就是令人头疼的问题,因为自己并没有学 ...

  8. 【模板小程序】链表排序(qsort/insert_sort/merge_sort)

    前言 本文章整理了链表排序的三种方法,分别是快速排序.插入排序.归并排序.为适应不同用途,先给出常用的int版本,再在此基础上抽象出类模板. 目录 一.针对整数的版本(常用) 文中链表定义 链表相关操 ...

  9. 线性表概述及单链表的Java实现

    一.线性表概述 线性表是指一组数据元素之间具有线性关系的元素序列,它表现为:除第一个元素没有直接前驱元素.最后一个元素没有直接后继元素外,其余所有元素都有且仅有一个直接前驱元素和直接后继元素. 根据存 ...

随机推荐

  1. win10编译jpeglib

    jpeglib看名字都大概知道和图像格式jpg或jpeg有关了,是一个常用的图像处理软件都会依赖的开源库. 首先去官网下载jpeglib的源码,直接取这里下载:http://www.ijg.org/f ...

  2. java常用第三方类库

    Guava:来自Google的常用类库 Apache Commons:来自Apache的常用类库 Mockito:主要用于单元测试的mock DBUnit:测试中管理数据库测试数据 Rest Assu ...

  3. blueimp,预览遮罩范围控制

    blueimg gallery github地址:https://github.com/blueimp/Gallery/blob/master/README.md 使用前提,引用css和js < ...

  4. kaggle——贷款信用评估介绍

    介绍 对于金融机构的贷款业务来说,一个顾客的信用信息是极其重要的.因为只有了解客户的信用情况,才能决定是否通过客户的贷款申请.本次将会介绍如何根据用户的一些基本信息来判断顾客的信用或贷款偿还能力. 知 ...

  5. echart曲线图标识最大值、最小值、平均值

    option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAx ...

  6. [CISCN2019 华北赛区 Day1 Web1]Dropbox-phar文件能够上传到服务器端实现任意文件读取

    0x00知识点 phar是什么: 我们先来了解一下流包装 大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://,zlib://或php://.例如常见的 include('php ...

  7. YouTube推出慈善组合工具,能引国内视频网站跟风吗?

    互联网的出现不仅仅让大众的工作和生活更便利,更深度改变着传统事物的形态,让其被更多人广泛地认知并接触到.如,原本在线下通过彩页.手册.横幅等进行宣传.募捐的慈善,就通过互联网展现出更为强大的影响力.而 ...

  8. goweb-模板引擎

    模板引擎 Go 为我们提供了 text/template 库和 html/template 库这两个模板引擎,模板引 擎通过将数据和模板组合在一起生成最终的 HTML,而处理器负责调用模板引擎并将引 ...

  9. 01 语言基础+高级:1-10 JDK8新特性_day12【函数式接口】

    day12[函数式接口] 主要内容自定义函数式接口函数式编程常用函数式接口 教学目标能够使用@FunctionalInterface注解能够自定义无参无返回函数式接口能够自定义有参有返回函数式接口能够 ...

  10. Java之同步方法处理继承Thread类的线程安全问题

    /** * 使用同步方法处理继承Thread类的方式中的线程安全问题 * */class Window4 extends Thread { private static int ticket = 10 ...