java 链表升序_LootCode-链表排序-Java

链表排序

0.来源

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 算法步骤

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

设定两个指针,最初位置分别为两个已经排序序列的起始位置;

比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

重复步骤 3 直到某一指针达到序列尾;

将另一序列剩下的所有元素直接复制到合并序列尾。

3.2.3 动态演示

10524e30bb2c16a7fdec00dac660d26f.gif

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

双向链表是一种常见的数据结构,与单向链表相比,它可以在节点之间进行双向遍历。在Java,我们可以使用类来实现双向链表。 下面是一个简单的Java程序,演示如何创建和输出双向链表: ```java // 双向链表节点类 class Node { public int data; public Node prev; public Node next; public Node(int data) { this.data = data; this.prev = null; this.next = null; } } // 双向链表类 class DoubleLinkedList { public Node head; public Node tail; public DoubleLinkedList() { this.head = null; this.tail = null; } // 在链表头部插入节点 public void insertAtHead(int data) { Node newNode = new Node(data); if (head == null) { head = newNode; tail = newNode; return; } newNode.next = head; head.prev = newNode; head = newNode; } // 在链表尾部插入节点 public void insertAtTail(int data) { Node newNode = new Node(data); if (tail == null) { head = newNode; tail = newNode; return; } newNode.prev = tail; tail.next = newNode; tail = newNode; } // 输出链表 public void printList() { Node current = head; while (current != null) { System.out.print(current.data + " "); current = current.next; } System.out.println(); } } // 测试类 public class Main { public static void main(String[] args) { DoubleLinkedList list = new DoubleLinkedList(); list.insertAtHead(1); list.insertAtHead(2); list.insertAtTail(3); list.insertAtTail(4); list.printList(); // 输出:2 1 3 4 } } ``` 在上面的程序,我们首先定义了一个`Node`类来表示双向链表的每个节点,其包含了当前节点的值、前一个节点和后一个节点。接着,我们定义了`DoubleLinkedList`类来表示整个双向链表,其包含了头节点和尾节点。 在`DoubleLinkedList`类,我们定义了`insertAtHead`和`insertAtTail`方法来在链表头部和尾部插入节点,分别需要创建一个新节点,并将它与当前链表的头节点或尾节点进行连接。 最后,我们定义了`printList`方法来输出整个链表,只需要从链表的头节点开始遍历,依次输出每个节点的值即可。 在`Main`类,我们通过`DoubleLinkedList`类创建了一个双向链表,并向其插入了四个节点。最后,我们调用`printList`方法输出整个链表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值