前置知识
- 数据结构-链表
- 数组排序算法:选择排序[解法1], 归并排序递归版[解法2],归并排序迭代法[解法3最优解]
- [归并部分基础]合并两个有序链表
- 如果您不满足于此, 笔者也提供冒泡排序,插入排序,快速排序的链表写法。以及,我们会在下文讨论为什么不说明希尔排序,堆排序, 因为两者不适合链表的特性。笔者不打算提供借助容器(数组)的写法, 以及衍生的3种非比较排序写法(计数排序,基数排序,桶排序的写法)
其实笔者也不太了解本篇由于时间原因, 因此鸽了部分篇幅
本篇是对链表的排序, 笔者认为读者阅读此篇理应掌握至少一个数组的排序算法。
测试链接力扣:排序链表
测试链接力扣补充:链表的插入排序
本篇重点:
- 熟悉选择排序的写法, 主要是讲处理数组的选择排序的思想应用于链表。主要是熟悉一下coding。
- 熟悉自上到底的归并排序链表写法, 采取的分治递归, 不过不是最优解,因为系统栈占据一定空间。
- 完全掌握迭代版本的归并排序写法。
原因: 它实现了数组不存在的排序性能。在比较排序中同时拥有时间复杂度
O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度:
O ( 1 ) O(1) O(1),还具有稳定性。
ps:数组迭代版本的快速排序是不具有稳定性的。迭代版本的归并排序的篇幅作者未更,可以自行查视频学习一下。
- 拓展性内容, 链表中的冒泡,插入,快速排序写法。算是数组排序算法对应应用到链表的实践吧。
链表快速排序的内容未更...
- 拓展性内容, 为什么希尔排序,堆排序不适合链表? 这些排序没有对应的实现代码, 因为性能太低了没有价值。
选择排序
要求:
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)
- 选择排序是什么?
- 请看VCR
// 数组中交换i和j位置的数
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 选择排序
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int minIndex, i = 0; i < arr.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
数组版本的选择排序思想是什么?
假设数组有n个元素,只需要对前面n-1个元素排序。
[0,i-1]
是已排序部分,[i,n-1]
是未排序部分。每次从未排序部分选出最小值,记录最小值的下标并与i位置数组元素交换。
那么改成链表版本的怎么做?
- 开局初始认为排序链表是空表, 整个链表是未排序部分。
- 第一次,从未排序部分链表找出最小节点。将其设置新头节点(搞一个新链表)。
- 接下来每次从未排序部分找出最小节点,并其从原链表中删除,并且尾插到排序部分链表。
- 遍历完原链表都会执行上述3的操作,这个时候返回新头节点就是排序链表的下标。
class Solution {
// 获取未排序链表中值最小的节点的前驱节点
public ListNode getSmallestNode(ListNode head) {
ListNode smallNode = head; // 记录当前最小节点
ListNode smallPrev = null; // 记录当前最小节点的前驱节点
ListNode prev = head, cur = head.next; // 初始化前驱节点和当前节点
// 遍历链表寻找最小值
while (cur != null) {
if (cur.val < smallNode.val) {
// 更新最小节点和它的前驱
smallPrev = prev;