[链表]–对链表进行插入排序和链表排序
题目链接
题目
对链表进行插入排序。
插入排序算法:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
示例
输入: 4->2->1->3
输出: 1->2->3->4
输入: -1->5->3->4->0
输出: -1->0->3->4->5
解析
- 首先 head 不能是 null;
- 有可能插入到 head 之前,所以定义一个 dummyHead,用于指向新的头节点;
- 比较之前,需要保存需要的节点,待排序节点 cur(初始为head.next) 以及其前驱节点 prevCur(有序序列的最后一个节点);
- 插入节点:
(1)首先如果 cur > prevCur,说明 cur 大于前面的所有结点,请继续遍历;
(2)反之,需要查找插入位置:从 temp=DummyHead 开始查找,因为插入时需要插入位置的前一个节点,比较 temp.next 和 cur 的大小关系;
(3)找到之后就准备插入待排序节点,在此之前需要 prevCur=cur.next; 用于保存下一个待排序节点;- cur = prevCur.next 继续遍历直至为 null。
代码实现
public class Solution147 {
/**
* Definition for singly-linked list
*/
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
/**
* 1.判空
* 2.定义一个 dummyHead 作为傀儡头结点, 因为有可能插入在 head 之前, 插入节点是需要前驱节点的, 同时, 用来指向新的头节点;
* 3.定义一个 prevCur 指向待排序节点的前驱节点, 也就是有序序列的最后一个节点;
* 4.遍历链表进行插入,由于第一个 head 可以看作有序, 所以从 head.next 出发;
* 5.首先如果 cur 大于 prevCur, 说明 cur 大于前面所有的,直接两个指针后移就好;
* 6.反之说明需要进行比较然后插入了:
* (1)由于插入需要前驱节点,所以在比较时, 从 temp=dummyHead 出发,比较 temp.next.val 和 cur.val 的大小;
* (2)查找到第一个不小于 cur 的节点停止;
* (3)插入在 temp 后面, 再插入之前需要注意将 cur.next 保存起来,需要进行下轮排序(直接 prevCur.Next=cur.next);
* 7.让 cur 指向下一个待排序节点继续遍历链表直至完结。
*/
public ListNode insertionSortList(ListNode head) {
// 排空
if (head == null) return null;
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = head.next;// 待排序结点
ListNode prevCur = head;// 指向待排序节点的前一个结点
while (cur != null) {
// cur 大于前面所有的结点
if (prevCur.val < cur.val) {
prevCur = cur;
cur = cur.next;
}else {
ListNode temp = dummyHead;
// 查找第一个不小于 cur 的前驱节点
while (temp.next.val < cur.val) {
temp = temp.next;
}
// 插入
prevCur.next = cur.next;
cur.next = temp.next;
temp.next = cur;
// 指向下一个待排序结点
cur = prevCur.next;
}
}
return dummyHead.next;
}
}
题目链接
题目
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例
输入:head = [4,2,1,3]
输出:[1,2,3,4]
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
输入:head = []
输出:[]
解析
插入排序,也可但意义不大;而且时间复杂度O(n^2)
需要时间复杂度O(n log n),也就是快排、堆排、归并三个,比较适合链表的就是归并,正好可以使用 合并两个有序链表
- 计算链表长度,归并排序需要将链表拆分成子链表进行排序;
- subLen 作为每次子链表的长度,初始为1(有序链表长度),每次是前一次的二倍;
- 将原链表分割成若干个长度为 subLen 的子链表(最后一组可能不够),两个一组进行合并(合并两个有序链表);合并之后有序链表的长度变成了subLen*2(最后一组可能不够);
- subLen变成前一次的二倍,继续分割合并直至整个链表排序完毕。
代码实现
public class Solution148 {
/**
* Definition for singly-linked list
*/
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
/**
* 自底向上的归并排序
*/
public ListNode sortList(ListNode head) {
if (head == null) return null;
ListNode cur = head;
int length = 0;
// 链表长度
while (cur != null) {
length++;
cur = cur.next;
}
// 归并
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
for (int subLen = 1; subLen < length; subLen *= 2) {
ListNode prev = dummyHead;// 已排序的尾结点
cur = dummyHead.next;
while (cur != null) {
// 链表1
ListNode head1 = cur;
for (int i = 0; i < subLen-1 && cur.next != null; i++) {
cur = cur.next;
}
// 链表2
ListNode head2 = cur.next;
cur.next = null;// 断开链表 1
cur = head2;
for (int i = 0; i < subLen-1 && cur != null && cur.next != null; i++) {
cur = cur.next;
}
// 判断是否有链表2
ListNode curNext = null;
if (cur != null) {
curNext = cur.next;
cur.next = null;// 断开链表2
}
ListNode temp = merge(head1, head2);
prev.next = temp;
// prev 再次指向已排序的尾结点
while (prev.next != null) {
prev = prev.next;
}
cur = curNext;
}
}
return dummyHead.next;
}
/**
* 合并两个有序链表
*/
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode temp = dummyHead;
// 比较并插入到新链表
while (head1 != null && head2 != null) {
if (head1.val <= head2.val) {
temp.next = head1;
temp = temp.next;
head1 = head1.next;
}else {
temp.next = head2;
temp = temp.next;
head2 = head2.next;
}
}
// 此时一个链表为 null
temp.next = (head1 == null) ? head2 : head1;
return dummyHead.next;
}
}
-----------------------------------------------------------------------------有始有终分割线----------------------------------------------------------------------------------