Skip to content

Commit c247c83

Browse files
author
subond.yu
committed
fix: 增加线性表和链表排序相关知识
1 parent 6f7f19a commit c247c83

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

linear_list.md

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
## 线性表 Linear List
2+
3+
1. [线性表的存储结构](#线性表的存储结构)
4+
1.1 [顺序存储](#顺序存储)
5+
1.2 [链式存储](#链式存储)
6+
7+
**线性表**:零个或多个数据元素的有限序列
8+
9+
**抽象的数据结构**
10+
11+
```
12+
定义
13+
线性表
14+
数据
15+
线性表的数据对象集合为{a1, a2, ..., an},每个数据元素类型均为DataType
16+
其中,除第一个元素a1外,每个元素都有且只有一个直接前驱元素
17+
除最后一个元素an外,每个元素都有且只有一个直接后驱元素
18+
数据元素之间的关系是一对一的关系
19+
操作
20+
初始化
21+
增,删,改,除
22+
空判断
23+
获取元素
24+
清空元素
25+
```
26+
27+
### 线性表的存储结构
28+
29+
* 顺序存储
30+
* 链式存储
31+
32+
### 顺序存储
33+
34+
1. 线性表顺序存储的三个基本属性
35+
36+
* 存储空间的起始位置
37+
* 线性表的最大存储容量
38+
* 线性表的当前长度
39+
40+
2. 顺序存储的优点及缺点
41+
* 优点:
42+
* 不需要为表示表中元素之间的逻辑关系而增加额外的存储空间
43+
* 可以快速存取表中的任意一个位置的元素
44+
* 缺点:
45+
* 插入和删除操作,需要移动大量的元素
46+
* 当线性表长度变化较大时,难以确定存储空间的容量
47+
* 造成存储空间的“碎片化”
48+
49+
数组就是最常见的顺序存储的线性表。
50+
51+
### 链式存储
52+
53+
1. 线性表链式存储的属性
54+
* 头指针,头节点
55+
* 指针域,数据域
56+
57+
头指针与头节点的区别:
58+
* 头指针
59+
* 头指针是指向链表第一个节点的指针,若链表有头节点,则是指向头节点的指针
60+
* 头指针具有标识作用,常用于标识链表的名字
61+
* 无论链表是否为空,头指针都不为空。**头指针是链表的必要元素**
62+
* 头节点
63+
* 头节点是为了操作的统一和方便设计的,放在第一个元素的节点之前,其数据一般无意义(可以存放链表的长度信息)。
64+
* 头节点不是链表的必要元素。
65+
66+
2. 链式存储的优点和缺点
67+
* 优点:
68+
* 采用链式存储单元存放线性表数据元素
69+
* 不需要预先分配固定大小的存储空间
70+
* 缺点:
71+
* 查找时间复杂度O(n)
72+
* 找到元素后,插入和删除操作的时间复杂度为O(1)
73+
74+
链式存储的线性表根据指针域的不同,可分为单向链表,循环链表和双向链表等。
75+
76+
### 单向链表
77+
78+
单向链表中每个节点有两个域,一个数据域和一个指针域。数据域用于表示单链表中的数据,指针域用于指向当前节点后驱节点的指针,简称为单链表。
79+
80+
### 循环链表
81+
82+
将单链表中的最后一个节点的指针指向该单链表的头节点,使得整个链表形成一个环。这种首尾相连的单链表称为单循环链表,简称循环链表。
83+
84+
### 双向链表
85+
86+
若在单链表中增加一个指向当前节点前驱节点的指针域,则构成双向链表。双向链表中包含三个域,一个数据域和两个指针域(一个指向后驱节点,一个指向前驱节点)。

linkedlist/linkedlist_sort.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
## 单链表的排序算法
2+
3+
* 1.[插入排序](#插入排序)
4+
* 2.[选择排序](#选择排序)
5+
* 3.[快速排序](#快速排序)
6+
* 4.[归并排序](#归并排序)
7+
8+
### 插入排序
9+
10+
插入排序可以通过直接交换节点得到。
11+
12+
```go
13+
/*
14+
功能:插入排序
15+
参数:传入链表头指针作为参数,返回排序后的头指针
16+
说明:时间复杂度O(n^2),空间复杂度O(1)
17+
第一步:选择插入的位置;
18+
第二步:如果在已排序的链表的尾部,则直接加入到尾部,否则,插入到选好的位置。注意:需要保存前驱节点。
19+
*/
20+
21+
func InsertSort(head *ListNode) *ListNode {
22+
if head == nil || head.Next == nil { return head }
23+
var pre, p, start, end *ListNode
24+
p, start, end, pre = head.Next, head, head, head
25+
var next *ListNode
26+
for p != nil {
27+
next, pre = p.Next, start
28+
for next != p && p.Val >= next.Val {
29+
next = next.Next
30+
pre = pre.Next
31+
}
32+
// 追加到已经排序的队尾
33+
if next == p {
34+
pend = p
35+
} else {
36+
pend.Next = p.Next
37+
p.Next = next
38+
pre.Next = p
39+
}
40+
p = pend.Next
41+
}
42+
return head
43+
}
44+
```
45+
46+
### 选择排序
47+
48+
```
49+
/*
50+
功能:选择排序(选择未排序序列中的最值,然后放到已排序的最前面或最后面,只交换节点的值)
51+
参数:输入链表的头指针,返回排序后的头指针
52+
说明:时间复杂度O(n^2),空间复杂度O(1)
53+
*/
54+
Node *SelectSort(Node *phead) {
55+
if(phead == NULL || phead->next == NULL) return phead;
56+
Node *pend = phead;
57+
while(pend->next != NULL) {
58+
Node *minNode = pend->next;
59+
Node *p = minNode->next;
60+
while(p != NULL) {
61+
if(p->val < minNode->val)
62+
minNode = p;
63+
p = p->next;
64+
}
65+
swap(minNode->val, pend->val);
66+
pend = pend->next;
67+
}
68+
return phead;
69+
}
70+
```
71+
72+
### 快速排序
73+
74+
```
75+
/*
76+
功能:快速排序,链表指向下一个元素的特性,partition是选择左闭合区间
77+
第一步,partiton,因为链表不支持随机访问元素,因此partiton中选取第一个节点值作为基准
78+
第二步,排序
79+
参数:输入链表头指针,输出排序后的头指针
80+
说明:平均时间复杂度O(nlogn),空间复杂度O(1)
81+
*/
82+
Node *Partition(Node *low, Node *high) {
83+
//左闭合区间[low, high)
84+
int base = low->val;
85+
Node *location = low;
86+
for(Node *i = low->next; i != high; i = i->next) {
87+
if(i->val > base) {
88+
location = location->next;
89+
swap(location->val, i->val);
90+
}
91+
}
92+
swap(low->val, location->val);
93+
return location;
94+
}
95+
void QuickList(Node *phead, Node *tail) {
96+
//左闭合区间[phead, tail)
97+
if(phead != tail && phead->next != tail) {
98+
Node *mid = Partition(phead, tail);
99+
QuickList(phead, mid);
100+
QuickList(mid->next, tail);
101+
}
102+
}
103+
Node *QuickSort(Node *phead) {
104+
if(phead == NULL || phead->next == NULL) return phead;
105+
QuickList(phead, NULL);
106+
return phead;
107+
}
108+
```
109+
110+
### 归并排序
111+
112+
```
113+
/*
114+
功能:归并排序,交换链表节点
115+
第一步:先写归并函数;第二步:利用快慢指针找到链表中点,然后递归对子链进行排序
116+
参数:输出链表的头指针,输出排序后的头指针
117+
说明:时间复杂度O(nlogn),空间复杂度O(1)。归并排序应该算是链表排序中最佳的选择,保证最好和最坏的时间复杂度都是O(nlogn),而且在将空间复杂度由数组中O(n),降到链表中的O(1)
118+
*/
119+
Node *merge(Node *phead1, Node *phead2) {
120+
if(phead1 == NULL) return phead2;
121+
if(phead2 == NULL) return phead1;
122+
Node *res, *p;
123+
if(phead1->val < phead2->val) {
124+
res = phead1;
125+
phead1 = phead1->next;
126+
} else {
127+
res = phead2;
128+
phead2 = phead2->next;
129+
}
130+
p = res;
131+
while(phead1 != NULL && phead2 != NULL) {
132+
if(phead1->val < phead2->val) {
133+
p->next = phead1;
134+
phead1 = phead1->next;
135+
} else {
136+
p->next = phead2;
137+
phead2 = phead2->next;
138+
}
139+
p = p->next;
140+
}
141+
if(phead1 != NULL) p->next = phead1;
142+
else if(phead2 != NULL) p->next = phead2;
143+
return res;
144+
}
145+
Node *MergeSort(Node *phead) {
146+
if(phead == NULL || phead->next == NULL) return phead;
147+
Node *fast = phead;
148+
Node *slow = phead;
149+
while(fast->next != NULL && fast->next->next != NULL) {
150+
fast = fast->next->next;
151+
slow = slow->next;
152+
}
153+
fast = slow;
154+
slow = slow->next;
155+
fast->next = NULL;
156+
fast = MergeSort(phead);
157+
slow = MergeSort(slow);
158+
return merge(fast, slow);
159+
}
160+
```

0 commit comments

Comments
 (0)