Algorithm
-
-
- [🎯 问题描述](#🎯 问题描述)
- [🛠️ 思路与方法](#🛠️ 思路与方法)
- [🧩 归并排序的原理](#🧩 归并排序的原理)
- [📚 解决方法:归并排序](#📚 解决方法:归并排序)
- [💻 C++ 实现](#💻 C++ 实现)
- [🧠 逐步解释](#🧠 逐步解释)
- [✅ 复杂度分析](#✅ 复杂度分析)
- [🧩 小结](#🧩 小结)
- [💡 其他解法(简单介绍)](#💡 其他解法(简单介绍))
-
🎯 问题描述
给定一个链表的头节点 head
,要求将链表按升序排序,并返回排序后的链表。
🛠️ 思路与方法
这个问题本质上是一个链表排序 问题,和常见的数组排序问题类似。常见的排序算法有很多种,最直接的方法是使用 归并排序(Merge Sort) 。因为归并排序具有 O(n log n) 的时间复杂度,而且对于链表来说,归并排序非常适用,它不需要像快速排序那样随机访问数据。
归并排序的优点:
- 时间复杂度:O(n log n)
- 空间复杂度:O(1),对于链表排序,我们可以使用递归实现归并排序,这样不需要额外的空间开销。
🧩 归并排序的原理
- 分割:递归地将链表分成两半,直到每个子链表只有一个节点为止。
- 合并:逐层合并这些子链表,使得每个子链表都是排序的。
📚 解决方法:归并排序
- 链表分割:我们可以使用快慢指针来找到链表的中点,将链表分为两半。
- 归并操作:递归地对两半链表进行排序,然后合并两部分。
💻 C++ 实现
cpp
#include <iostream>
using namespace std;
// Definition for singly-linked list.
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
// 归并排序主函数
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head; // 递归终止条件:空链表或只有一个节点
// 步骤1: 分割链表
ListNode* mid = getMiddle(head);
ListNode* right = mid->next;
mid->next = nullptr; // 将左半部分断开
// 步骤2: 递归排序左半部分和右半部分
ListNode* left = sortList(head);
right = sortList(right);
// 步骤3: 合并两个有序链表
return merge(left, right);
}
private:
// 获取链表的中间节点
ListNode* getMiddle(ListNode* head) {
if (!head) return nullptr;
ListNode* slow = head;
ListNode* fast = head;
// 快慢指针:fast 每次移动 2 步,slow 每次移动 1 步
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// 合并两个有序链表
ListNode* merge(ListNode* left, ListNode* right) {
ListNode dummy(0);
ListNode* curr = &dummy;
// 合并过程
while (left && right) {
if (left->val <= right->val) {
curr->next = left;
left = left->next;
} else {
curr->next = right;
right = right->next;
}
curr = curr->next;
}
// 连接剩余部分
if (left) curr->next = left;
if (right) curr->next = right;
return dummy.next;
}
};
int main() {
// 示例:创建一个链表
ListNode* head = new ListNode(4);
head->next = new ListNode(2);
head->next->next = new ListNode(1);
head->next->next->next = new ListNode(3);
Solution solution;
ListNode* sortedList = solution.sortList(head);
// 打印排序后的链表
ListNode* p = sortedList;
while (p) {
cout << p->val << " ";
p = p->next;
}
return 0;
}
🧠 逐步解释
-
sortList
函数:- 递归地分割链表。
- 如果链表为空或者只有一个节点,则直接返回该链表。
- 通过调用
getMiddle
找到链表的中间节点,并将链表分成左右两部分。 - 分别对左半部分和右半部分进行排序。
- 合并左右两个已经排序的链表,并返回结果。
-
getMiddle
函数:- 使用快慢指针来找到链表的中点。
slow
每次走一步,fast
每次走两步,直到fast
或fast->next
为nullptr
,此时slow
就指向链表的中点。
- 使用快慢指针来找到链表的中点。
-
merge
函数:- 将两个已经排序的链表合并成一个排序好的链表。
- 使用一个虚拟头节点
dummy
来简化合并过程,最终返回合并后的链表。
✅ 复杂度分析
- 时间复杂度 :O(n log n),因为每次分割都将链表分成两部分,递归深度为
log n
,而合并两个链表的时间是线性的O(n)
,因此总的时间复杂度是O(n log n)
。 - 空间复杂度 :O(log n),由于递归调用栈的空间使用,空间复杂度为
O(log n)
。如果使用迭代方式来替代递归,空间复杂度可以进一步优化为O(1)
。
🧩 小结
- 归并排序非常适合链表排序,尤其是当链表非常长时,归并排序能够提供一个稳定的
O(n log n)
时间复杂度。 - 通过链表的分割和合并操作,我们可以非常高效地完成链表的排序。
💡 其他解法(简单介绍)
除了归并排序,还可以使用快速排序或堆排序等方法,但对于链表来说,归并排序是最常见的选择,因为它适合链表的结构,不需要随机访问。