数据结构复习
C语言基础
定义结构体
1 |
|
动态内存分配
1 |
|
指针和链表的结合
1 | // 直接用结构体用. 指针用结构体用-> |
数组与指针的关系
1 | int main() { |
递归
1 | // 二叉树的前序遍历 |
时间复杂度和空间复杂度
时间复杂度
- 时间复杂度的重点在于关注最坏情况下的运行时间。比如,在冒泡排序中,我们可能需要比较所有元素,这使得时间复杂度为O(n^2);
- 一般是以循环,递归,二分去计算的
- 二分查找log(n), 循环遍历是n
- 遍历数组
1
2
3
4
5
6void traverseArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
}
// 时间复杂度:O(n) - 嵌套循环
1
2
3
4
5
6
7
8void nestedLoops(int arr[], int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("i: %d, j: %d\n", i, j);
}
}
}
// 时间复杂度:O(n^2) - 二分查找
1
2
3
4
5
6
7
8
9
10
11
12int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// 时间复杂度:O(log n)
// 二分查找写法很多,这里只是例子
空间复杂度
- 无额外空间(原地操作)
1
2
3
4
5
6void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 空间复杂度:O(1)(只需要一个额外变量) - 递归算法
1
2
3
4
5int factorial(int n) {
if (n == 1) return 1;
return n * factorial(n - 1);
}
// 空间复杂度:O(n)(递归调用栈消耗) - 动态规划
1
2
3
4
5
6
7
8
9
10int fibonacci(int n) {
int dp[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 空间复杂度:O(n)(存储数组 dp[] 占用)
线性表
顺序表和链表
顺序表
可以理解成数组
功能说明
- 初始化:动态分配内存并设置初始容量和长度。
- 销毁:释放顺序表的数据和结构体本身的内存。
- 清空:将表长设为 0,但不释放内存。
- 求表长:直接返回当前表的长度。
- 判断是否为空:判断表长是否为 0。
- 取值:根据索引取出指定位置的值。
- 查找:在顺序表中查找第一个匹配值的索引。
- 插入:在指定位置插入一个新元素。
- 删除:删除指定位置的元素,并返回被删除的值。
- 打印表:打印顺序表中的所有元素。
其实每种数据结构要做的都差不多,第一个学好了会怎么写了后面的光记住操作就好了
有for的时间复杂度是O(n)其他的事O(1)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 顺序表的定义
typedef struct {
int* data; // 动态数组
int length; // 当前表长
int capacity; // 最大容量
} SeqList;
// 初始化顺序表
SeqList* InitList(int capacity) {
SeqList* list = (SeqList*)malloc(sizeof(SeqList));
if (!list) {
printf("内存分配失败!\n");
return NULL;
}
list->data = (int*)malloc(capacity * sizeof(int));
if (!list->data) {
printf("内存分配失败!\n");
free(list);
return NULL;
}
list->length = 0;
list->capacity = capacity;
return list;
}
// 销毁顺序表
void DestroyList(SeqList* list) {
if (list) {
free(list->data);
free(list);
}
}
// 清空顺序表
void ClearList(SeqList* list) {
if (list) {
list->length = 0;
}
}
// 求表长
int GetLength(SeqList* list) {
if (list) {
return list->length;
}
return -1; // 错误标志
}
// 判断是否为空
bool IsEmpty(SeqList* list) {
return list && list->length == 0; // 要存在才能判空
}
// 取值
bool GetValue(SeqList* list, int index, int* value) {
if (!list || index < 0 || index >= list->length) {
return false; // 无效操作
}
*value = list->data[index];
return true;
}
// 查找值的位置
int Find(SeqList* list, int value) {
if (!list) {
return -1; // 错误标志
}
for (int i = 0; i < list->length; i++) {
if (list->data[i] == value) {
return i; // 返回第一个匹配的位置
}
}
return -1; // 未找到
}
// 插入元素
bool Insert(SeqList* list, int index, int value) {
if (!list || index < 0 || index > list->length || list->length >= list->capacity) {
return false; // 无效操作
}
for (int i = list->length; i > index; i--) {
list->data[i] = list->data[i - 1];
}
list->data[index] = value;
list->length++;
return true;
}
// 删除元素
bool Delete(SeqList* list, int index, int* deletedValue) {
if (!list || index < 0 || index >= list->length) {
return false; // 无效操作
}
*deletedValue = list->data[index];
for (int i = index; i < list->length - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->length--;
return true;
}
// 打印顺序表
void PrintList(SeqList* list) {
if (!list) {
printf("顺序表不存在!\n");
return;
}
printf("顺序表元素:");
for (int i = 0; i < list->length; i++) {
printf("%d ", list->data[i]);
}
printf("\n");
}
// 主函数测试
int main() {
SeqList* list = InitList(10); // 初始化容量为10的顺序表
if (!list) return -1;
// 插入元素
Insert(list, 0, 10);
Insert(list, 1, 20);
Insert(list, 2, 30);
PrintList(list); // 输出:10 20 30
// 删除元素
int deletedValue;
Delete(list, 1, &deletedValue);
printf("删除的元素:%d\n", deletedValue);
PrintList(list); // 输出:10 30
// 查找元素
int index = Find(list, 30);
printf("查找30的位置:%d\n", index); // 输出:1
// 取值
int value;
if (GetValue(list, 1, &value)) {
printf("取出的值:%d\n", value); // 输出:30
}
// 清空列表
ClearList(list);
PrintList(list); // 输出:空表
// 判断是否为空
printf("顺序表是否为空:%s\n", IsEmpty(list) ? "是" : "否");
// 销毁顺序表
DestroyList(list);
return 0;
}
空间复杂度事O(n)
链表
顺序表:适合需要频繁访问和快速随机访问的场景(如数组),但插入和删除效率低,且内存大小固定。
链表:适合需要频繁插入和删除的场景,动态内存管理,灵活扩展,但访问效率较低。
- 链表的定义和基本操作实现初始化链表:通过返回 NULL 来表示一个空链表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 链表节点定义
typedef struct Node {
int data; // 数据域
struct Node* next; // 指向下一个节点的指针
} Node;
// 初始化链表
Node* InitList() {
return NULL; // 空链表初始化,返回 NULL
}
// 取值(根据索引获取值)
int GetValue(Node* head, int index) {
Node* current = head;
int count = 0;
while (current != NULL) {
if (count == index) {
return current->data;
}
current = current->next;
count++;
}
printf("索引越界\n");
return -1; // 返回一个错误标志
}
// 查找(返回第一个匹配元素的索引)
int Find(Node* head, int value) {
Node* current = head;
int index = 0;
while (current != NULL) {
if (current->data == value) {
return index;
}
current = current->next;
index++;
}
return -1; // 如果未找到,返回 -1
}
// 插入(在指定位置插入一个新节点)
int Insert(Node** head, int index, int value) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
printf("内存分配失败\n");
return 0;
}
newNode->data = value;
if (index == 0) {
newNode->next = *head;
*head = newNode;
return 1;
}
Node* current = *head;
int count = 0;
while (current != NULL && count < index - 1) {
current = current->next;
count++;
}
if (current == NULL) {
printf("插入位置无效\n");
free(newNode);
return 0;
}
newNode->next = current->next;
current->next = newNode;
return 1;
}
// 删除(删除指定位置的节点)
int Delete(Node** head, int index) {
if (*head == NULL) {
printf("链表为空\n");
return 0;
}
Node* current = *head;
// 删除头节点
if (index == 0) {
*head = current->next;
free(current);
return 1;
}
int count = 0;
while (current != NULL && count < index - 1) {
current = current->next;
count++;
}
if (current == NULL || current->next == NULL) {
printf("删除位置无效\n");
return 0;
}
Node* temp = current->next;
current->next = temp->next;
free(temp);
return 1;
}
// 打印链表
void PrintList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 主函数测试链表操作
int main() {
Node* list = InitList(); // 初始化链表
// 插入元素
Insert(&list, 0, 10);
Insert(&list, 1, 20);
Insert(&list, 1, 15);
PrintList(list); // 输出:10 -> 15 -> 20 -> NULL
// 取值
int value = GetValue(list, 1);
printf("索引1的值:%d\n", value); // 输出:15
// 查找元素
int index = Find(list, 20);
printf("值20的索引位置:%d\n", index); // 输出:2
// 删除元素
Delete(&list, 1);
PrintList(list); // 输出:10 -> 20 -> NULL
return 0;
}
取值:通过索引查找链表中的元素。遍历链表直到找到指定位置。
查找:遍历链表,找到第一个匹配的元素并返回其索引。
插入:在指定位置插入一个新节点。如果插入位置是头部,则直接修改头指针;如果是中间或尾部,需要遍历到该位置并插入新节点。
删除:删除指定位置的节点。若删除的是头节点,需要直接修改头指针;删除中间或尾部节点,需要调整前一个节点的指针。
除了初始化其他操作都是O(n),不过实际只用可以用脑子优化
Node* 是指向链表节点的指针,通常用来操作单个节点。
Node** 是指向 Node* 的指针,常用于需要修改链表头指针或操作指针本身的场景。
通过使用 Node**,可以直接修改头指针,支持链表的动态操作(如插入头节点、删除头节点)。
熟练起来方便,课本还提供了虚拟头节点的方法
1 |
|
循环,双向在此基础上简单的加指针就好了
栈和队列
栈
重点是要记住栈事是后进先出的元素LIFO(last in first out)
1 |
|
操作解释
初始化栈:设置栈顶指针 top 为 -1,表示栈为空。
判断栈空:检查栈顶指针是否为 -1。
求栈长:栈的长度就是栈顶指针加 1。
清空栈:将栈顶指针重置为 -1。
销毁栈:对于静态分配的栈,销毁操作不需要额外操作。若使用动态分配(malloc),则可以释放内存。
入栈:判断栈是否满,若未满,则将元素压入栈中并更新栈顶指针。
出栈:判断栈是否空,若不为空,取出栈顶元素并更新栈顶指针。
读栈顶元素值:判断栈是否空,若不为空,则读取栈顶元素。
顺序栈是通过数组实现的栈,主要使用一个栈顶指针 top 来标记栈顶元素的位置。
各种操作(如入栈、出栈、查看栈顶元素)都依赖于栈顶指针的更新。
栈的应用,递归
递归(Recursion)是一个函数通过直接或间接调用自身来解决问题。递归的核心在于将问题分解成较小的子问题,每次递归都处理一个更小的子问题,直到达到一个简单的基本情况(称为递归终止条件),然后逐步返回结果。
用实习阶层做个小例子
1 |
|
推荐联系题
https://www.luogu.com.cn/problem/B3623 // 递归实现排列型枚举
https://www.luogu.com.cn/problem/B3622 //递归实现指数型枚举
https://www.luogu.com.cn/problem/P10448 //组合型枚举
队列
重点是记住队列是先进先出的元素FIFO(first in first out)
队列的基本操作
初始化队列
判断队列是否为空
判断队列是否满
入队(enqueue)
出队(dequeue)
读取队头元素(front)
获取队列的长度
清空队列
销毁队列
1 |
|
头尾相连的循环队列,可以充分利用空间