Skip to content

Commit 6d2057d

Browse files
committed
【update】链表排序
1 parent d037bff commit 6d2057d

File tree

3 files changed

+269
-63
lines changed

3 files changed

+269
-63
lines changed

C-算法/专题-A-数据结构.md

Lines changed: 199 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ Index
2828
- [合并排序链表](#合并排序链表)
2929
- [两个链表的第一个公共节点](#两个链表的第一个公共节点)
3030
- [链表排序](#链表排序)
31-
- [分隔链表(Partition List)](#分隔链表partition-list)
32-
- [链表快排(Sort List)](#链表快排sort-list)
31+
- [链表快排](#链表快排)
32+
- [链表归并](#链表归并)
33+
- [链表插入排序](#链表插入排序)
3334
- [二维数组](#二维数组)
3435
- [二分查找](#二分查找)
3536
- [搜索二维矩阵 1](#搜索二维矩阵-1)
@@ -785,64 +786,7 @@ public:
785786
## 链表排序
786787
> [链表排序(冒泡、选择、插入、快排、归并、希尔、堆排序) - tenos](https://www.cnblogs.com/TenosDoIt/p/3666585.html) - 博客园
787788

788-
### 分隔链表(Partition List)
789-
> LeetCode/[86. 分隔链表](https://leetcode-cn.com/problems/partition-list/description/)
790-
791-
**问题描述**
792-
```
793-
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
794-
795-
你应当保留两个分区中每个节点的初始相对位置。
796-
797-
示例:
798-
输入: head = 1->4->3->2->5->2, x = 3
799-
输出: 1->2->2->4->3->5
800-
```
801-
802-
**思路**
803-
- 链表快排的中间操作;
804-
- 新建两个链表,分别保存小于 x 和大于等于 x 的,最后拼接;
805-
- 因为要求节点的相对位置不变,所以这么写比较方便;
806-
- 一般来说,链表快排有两种写法:一种是交换节点内的值,一种是交换节点;该写法适用于后者。
807-
- ~~如果是用在链表快排中,可以把头节点作为 x,最后把 x 插进 lo 和 hi 链表的中间;~~
808-
- ~~这种写法不适合用在链表快排中,因为这里有拼接操作;~~
809-
- ~~在实际链表快排中 partition 操作只对中间一部分执行,如果需要拼接,容易出错。~~
810-
811-
**Python**
812-
```python
813-
# Definition for singly-linked list.
814-
# class ListNode:
815-
# def __init__(self, x):
816-
# self.val = x
817-
# self.next = None
818-
819-
class Solution:
820-
def partition(self, h, x):
821-
"""
822-
:type h: ListNode
823-
:type x: int
824-
:rtype: ListNode
825-
"""
826-
l = lo = ListNode(0)
827-
r = hi = ListNode(0)
828-
829-
while h:
830-
if h.val < x:
831-
l.next = h # Python 中不支持 l = l.next = h 的写法,C++ 指针可以
832-
l = l.next
833-
else:
834-
r.next = h # Python 中不支持 r = r.next = h 的写法,C++ 指针可以
835-
r = r.next
836-
837-
h = h.next
838-
839-
r.next = None # 因为尾节点可能不小于 x,所以需要断开
840-
l.next = hi.next
841-
842-
return lo.next
843-
```
844-
845-
### 链表快排(Sort List)
789+
### 链表快排
846790
> LeetCode/[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/)
847791

848792
**问题描述**
@@ -971,6 +915,201 @@ public:
971915
};
972916
```
973917
918+
### 链表归并
919+
> LeetCode/[148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/)
920+
921+
**问题描述**
922+
```
923+
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
924+
925+
示例 1:
926+
输入: 4->2->1->3
927+
输出: 1->2->3->4
928+
示例 2:
929+
输入: -1->5->3->4->0
930+
输出: -1->0->3->4->5
931+
```
932+
933+
**思路**
934+
- 用快慢指针的方法找到链表中间节点,然后递归的对两个子链表排序,把两个排好序的子链表合并成一条有序的链表
935+
- 归并排序比较适合链表,它可以保证了最好和最坏时间复杂度都是 `O(NlogN)`,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(n)降到了 `O(1)`
936+
- 因为链表快排中只能使用第一个节点作为枢纽,所以不能保证时间复杂度
937+
- 还是使用 C++
938+
939+
**C++**
940+
```C++
941+
/**
942+
* Definition for singly-linked list.
943+
* struct ListNode {
944+
* int val;
945+
* ListNode *next;
946+
* ListNode(int x) : val(x), next(NULL) {}
947+
* };
948+
*/
949+
950+
class Solution {
951+
ListNode* merge(ListNode *h1, ListNode *h2) { // 排序两个链表
952+
if (h1 == nullptr) return h2;
953+
if (h2 == nullptr) return h1;
954+
955+
ListNode* h; // 合并后的头结点
956+
if (h1->val < h2->val) {
957+
h = h1;
958+
h1 = h1->next;
959+
} else {
960+
h = h2;
961+
h2 = h2->next;
962+
}
963+
964+
ListNode* p = h;
965+
while (h1 && h2) {
966+
if (h1->val < h2->val) {
967+
p->next = h1;
968+
h1 = h1->next;
969+
} else {
970+
p->next = h2;
971+
h2 = h2->next;
972+
}
973+
p = p->next;
974+
}
975+
976+
if (h1) p->next = h1;
977+
if (h2) p->next = h2;
978+
979+
return h;
980+
}
981+
982+
public:
983+
ListNode* sortList(ListNode* h) {
984+
if (h == nullptr || h->next == nullptr)
985+
return h;
986+
987+
auto f = h, s = h; // 快慢指针 fast & slow
988+
while (f->next && f->next->next) {
989+
f = f->next->next;
990+
s = s->next;
991+
}
992+
f = s->next; // 中间节点
993+
s->next = nullptr; // 断开
994+
995+
h = sortList(h); // 前半段排序
996+
f = sortList(f); // 后半段排序
997+
998+
return merge(h, f);
999+
}
1000+
};
1001+
```
1002+
1003+
### 链表插入排序
1004+
> LeetCode/[147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/description/)
1005+
1006+
**问题描述**
1007+
```
1008+
对链表进行插入排序。
1009+
1010+
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
1011+
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
1012+
重复直到所有输入数据插入完为止。
1013+
1014+
示例 1:
1015+
输入: 4->2->1->3
1016+
输出: 1->2->3->4
1017+
示例 2:
1018+
输入: -1->5->3->4->0
1019+
输出: -1->0->3->4->5
1020+
```
1021+
1022+
<div align="center"><img src="../_assets/Insertion-sort-example-300px.gif" height="" /></div>
1023+
1024+
- 插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
1025+
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
1026+
1027+
**代码 1 - 非原地**
1028+
- 实际上,对链表来说,不存在是否原地的问题,不像数组
1029+
- 这里所谓的非原地是相对数组而言的,因此下面的代码只针对链表,不适用于数组。
1030+
```C++
1031+
/**
1032+
* Definition for singly-linked list.
1033+
* struct ListNode {
1034+
* int val;
1035+
* ListNode *next;
1036+
* ListNode(int x) : val(x), next(NULL) {}
1037+
* };
1038+
*/
1039+
class Solution {
1040+
public:
1041+
ListNode* insertionSortList(ListNode* h) {
1042+
if (h == nullptr || h->next == nullptr)
1043+
return h;
1044+
1045+
// 因为是链表,所以可以重新开一个新的链表来保存排序好的部分;
1046+
// 不存在空间上的问题,这一点不像数组
1047+
auto H = new ListNode(0);
1048+
1049+
auto pre = H;
1050+
auto cur = h;
1051+
ListNode* nxt;
1052+
while (cur) {
1053+
while (pre->next && pre->next->val < cur->val) {
1054+
pre = pre->next;
1055+
}
1056+
1057+
nxt = cur->next; // 记录下一个要遍历的节点
1058+
// 把 cur 插入 pre 和 pre->next 之间
1059+
cur->next = pre->next;
1060+
pre->next = cur;
1061+
1062+
// 重新下一轮
1063+
pre = H;
1064+
cur = nxt;
1065+
}
1066+
1067+
h = H->next;
1068+
delete H;
1069+
return h;
1070+
}
1071+
};
1072+
```
1073+
1074+
**代码 2 - 原地**
1075+
- 即不使用新链表,逻辑与数组一致
1076+
```C++
1077+
class Solution {
1078+
public:
1079+
ListNode* insertionSortList(ListNode* h) {
1080+
if (h == nullptr || h->next == nullptr)
1081+
return h;
1082+
1083+
auto beg = new ListNode(0);
1084+
beg->next = h;
1085+
auto end = h; // (beg, end] 指示排好序的部分
1086+
1087+
auto p = h->next; // 当前待排序的节点
1088+
while (p) {
1089+
auto pre = beg;
1090+
auto cur = beg->next; // p 将插入到 pre 和 cur 之间
1091+
while (cur != p && p->val >= cur->val) {
1092+
cur = cur->next;
1093+
pre = pre->next;
1094+
}
1095+
1096+
if (cur == p) {
1097+
end = p;
1098+
} else {
1099+
end->next = p->next;
1100+
p->next = cur;
1101+
pre->next = p;
1102+
}
1103+
p = end->next;
1104+
}
1105+
1106+
h = beg->next;
1107+
delete beg;
1108+
return h;
1109+
}
1110+
};
1111+
```
1112+
9741113

9751114
# 二维数组
9761115

C-算法/专题-B-双指针.md

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<div align="center"><img src="../_assets/TIM截图20180928102605.png" height="" /></div>
1616

1717
- 一般用于寻找满足某个条件的**连续区间**
18+
-**链表**相关问题中经常会使用**快慢双指针**来寻找某个节点
1819
- **分离双指针**
1920
<div align="center"><img src="../_assets/TIM截图20180928103003.png" height="" /></div>
2021

@@ -29,6 +30,7 @@ RoadMap
2930
- [首尾双指针](#首尾双指针)
3031
- [同向双指针](#同向双指针)
3132
- [分离双指针](#分离双指针)
33+
- [链表相关](#链表相关)
3234
- [其他](#其他)
3335

3436
Index
@@ -59,7 +61,10 @@ Index
5961
- [II](#ii)
6062
- [合并两个有序数组(Merge Sorted Array)](#合并两个有序数组merge-sorted-array)
6163
- [链表相关](#链表相关)
62-
- [链表快排(Sort List)](#链表快排sort-list)
64+
- [分隔链表(Partition List)](#分隔链表partition-list)
65+
- [链表排序](#链表排序)
66+
- [链表快排](#链表快排)
67+
- [链表归并](#链表归并)
6368
- [旋转链表(Rotate List)](#旋转链表rotate-list)
6469
- [其他](#其他)
6570
- [最小区间(Smallest Range)](#最小区间smallest-range)
@@ -1251,8 +1256,70 @@ class Solution:
12511256

12521257
# 链表相关
12531258

1254-
## 链表快排(Sort List)
1255-
> ./数据结构/[链表快排](./专题-A-数据结构#链表快排sort-list)
1259+
## 分隔链表(Partition List)
1260+
> LeetCode/[86. 分隔链表](https://leetcode-cn.com/problems/partition-list/description/)
1261+
1262+
**问题描述**
1263+
```
1264+
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
1265+
1266+
你应当保留两个分区中每个节点的初始相对位置。
1267+
1268+
示例:
1269+
输入: head = 1->4->3->2->5->2, x = 3
1270+
输出: 1->2->2->4->3->5
1271+
```
1272+
1273+
**思路**
1274+
- 链表快排的中间操作;
1275+
- 新建两个链表,分别保存小于 x 和大于等于 x 的,最后拼接;
1276+
- 因为要求节点的相对位置不变,所以这么写比较方便;
1277+
- 一般来说,链表快排有两种写法:一种是交换节点内的值,一种是交换节点;该写法适用于后者。
1278+
- ~~如果是用在链表快排中,可以把头节点作为 x,最后把 x 插进 lo 和 hi 链表的中间;~~
1279+
- ~~这种写法不适合用在链表快排中,因为这里有拼接操作;~~
1280+
- ~~在实际链表快排中 partition 操作只对中间一部分执行,如果需要拼接,容易出错。~~
1281+
1282+
**Python**
1283+
```python
1284+
# Definition for singly-linked list.
1285+
# class ListNode:
1286+
# def __init__(self, x):
1287+
# self.val = x
1288+
# self.next = None
1289+
1290+
class Solution:
1291+
def partition(self, h, x):
1292+
"""
1293+
:type h: ListNode
1294+
:type x: int
1295+
:rtype: ListNode
1296+
"""
1297+
l = lo = ListNode(0)
1298+
r = hi = ListNode(0)
1299+
1300+
while h:
1301+
if h.val < x:
1302+
l.next = h # Python 中不支持 l = l.next = h 的写法,C++ 指针可以
1303+
l = l.next
1304+
else:
1305+
r.next = h # Python 中不支持 r = r.next = h 的写法,C++ 指针可以
1306+
r = r.next
1307+
1308+
h = h.next
1309+
1310+
r.next = None # 因为尾节点可能不小于 x,所以需要断开
1311+
l.next = hi.next
1312+
1313+
return lo.next
1314+
```
1315+
1316+
## 链表排序
1317+
1318+
### 链表快排
1319+
> ./数据结构/[链表快排](./专题-A-数据结构#链表快排)
1320+
1321+
### 链表归并
1322+
> ./数据结构/[链表归并](./专题-A-数据结构#链表归并)
12561323
12571324
## 旋转链表(Rotate List)
12581325
> ./数据结构/[旋转链表](./专题-A-数据结构#旋转链表rotate-list)
89.8 KB
Loading

0 commit comments

Comments
 (0)