147.对链表进行插入排序
题目叙述
对链表进行插入排序。插入排序的动画演示如下。(图片描述可在 LeetCode 原题查看)
插入排序算法:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
模式识别
本题是典型的排序问题,具体为对链表进行插入排序。插入排序的核心思想是将未排序的数据插入到已排序序列的合适位置,这是一个逐步构建有序序列的过程。对于链表这种数据结构,插入操作相对方便,只需修改指针指向即可,而不需要像数组那样进行大量元素的移动。
考点分析
- 链表操作:需要熟练掌握链表的遍历、插入等基本操作,包括指针的移动和修改。
- 插入排序算法:理解插入排序的原理,并将其应用到链表上。
- 边界条件处理:处理链表为空、只有一个节点等特殊情况。
所有解法
- 插入排序法:从链表的第二个节点开始,依次将每个节点插入到前面已排序的链表中的合适位置。
- 其他排序算法(不推荐):理论上可以将链表转换为数组,使用更高效的排序算法(如快速排序、归并排序)对数组排序,然后再将排序后的数组转换回链表,但这种方法会增加额外的空间复杂度。
C 语言最优解法代码
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
struct ListNode {
int val; // 节点的值
struct ListNode *next; // 指向下一个节点的指针
};
// 对链表进行插入排序的函数
struct ListNode* insertionSortList(struct ListNode* head) {
// 如果链表为空或只有一个节点,直接返回原链表
if (head == NULL || head->next == NULL) {
return head;
}
// 创建一个虚拟头节点,方便处理插入操作
struct ListNode *dummy = (struct ListNode *)malloc(sizeof(struct ListNode));
dummy->val = 0;
dummy->next = head;
// 记录已排序部分的最后一个节点
struct ListNode *lastSorted = head;
// 当前待插入的节点
struct ListNode *curr = head->next;
while (curr != NULL) {
if (lastSorted->val <= curr->val) {
// 如果当前节点的值大于等于已排序部分的最后一个节点的值,直接移动指针
lastSorted = lastSorted->next;
} else {
// 否则,需要将当前节点插入到已排序部分的合适位置
struct ListNode *prev = dummy;
// 找到插入位置
while (prev->next->val <= curr->val) {
prev = prev->next;
}
// 断开当前节点与原链表的连接
lastSorted->next = curr->next;
// 将当前节点插入到合适位置
curr->next = prev->next;
prev->next = curr;
}
// 移动到下一个待插入的节点
curr = lastSorted->next;
}
// 返回排序后的链表头节点
struct ListNode *sortedHead = dummy->next;
// 释放虚拟头节点的内存
free(dummy);
return sortedHead;
}
复杂度分析
- 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是链表的长度。对于每个节点,最坏情况下需要遍历前面已排序的所有节点来找到插入位置。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级的额外空间,主要是虚拟头节点和几个指针变量。