链式存储
线性表之单链表
将线性表的个元素分布在存储器的不同存储块,称为结点,通过地址或指针建立元素之间的联系。
结点分为data域和next域,next域是一个指针,指向ai的直接后继ai+1的所在结点。next为NULL时表明该元素为链表最后一个元素。
头结点:data不重要,专门用来标记第一个元素的结点地址
结点类型描述:
1 2 3 4 5
| typedef struct node { data_t data; struct node *next; }listnode, *linklist;
|
所以有
1 2
| listnode A; linklist p = &A;
|
若指针p的值为NULL,则其不指向任何结点,此时取p->data或p->next是错误的
单链表的遍历
线性存储中,遍历基于表尾标识符last完成,索引i从0循环到last视为遍历一次。
链式存储中,节点之间由next进行后继连接,所以遍历应当从头节点开始,由head = head->next
方式进行索引(指针)更新
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int list_show(linklist H) { linklist p;
if (H == NULL) { printf("H is NULL\n"); return -1; }
p = H->next; while (p != NULL) { printf("%d ",p->data); p = p->next; }
return 0; }
|
单链表获取指定位置的节点地址
头节点只有Next域,没有data域;尾节点只有data域,Next域为NULL。
list_get()函数遍历到指定链表的pos位置,遍历到之后返回最后一个节点的指针。注意遍历可以有两种方式:
- 从头节点开始,初始化p = head,i = -1,遍历到i < pos时结束,此时的pos是包含头节点在内的位置,比如链表{1,2,6,3},元素2的位置是2,不是1;
- 从头节点之后的第一个结点开始,初始化p = head->next; i = 0,遍历到i < pos时结束,此时的pos是不包含头节点在内的位置,比如链表{1,2,6,3},元素2的位置是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
|
linklist list_get(linklist H, int pos) { linklist p; int i = -1;
if (H == NULL) { printf("H is NULL\n"); return NULL; }
if (pos == -1) { return H; }
p = H; while (i < pos) { p = p->next; if (p == NULL) { printf("pos is invalid\n"); return NULL; } i++; }
return p; }
|
单链表的其他操作
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
| #include <stdio.h> #include <stdlib.h> #include "linklist.h"
linklist list_create() { linklist head = (linklist)malloc(sizeof(listnode));
if (head == NULL) { printf("memory allocation failed\n"); return NULL; } head->next = NULL; return head; }
int list_insert(linklist head, data_t value, int pos) { if (head == NULL) return -1;
if (pos == 0) { linklist new_node = (linklist)malloc(sizeof(listnode)); if (new_node == NULL) { printf("memory allocation failed\n"); return -1; }
new_node->data = value; new_node->next = head->next; head->next = new_node; return 0; }
linklist prev_node = list_get(head, pos-1); if (prev_node == NULL) { printf("Invalid position\n"); return -1; }
linklist new_node = (linklist)malloc(sizeof(listnode)); if (new_node == NULL) { printf("memory allocation failed\n"); return -1; }
new_node->data = value; new_node->next = prev_node->next; prev_node->next = new_node;
return 0; }
linklist list_get(linklist H, int pos) { linklist p; int i = -1;
if (H == NULL) { printf("H is NULL\n"); return NULL; }
if (pos == -1) { return H; }
p = H; while (i < pos) { p = p->next; if (p == NULL) { printf("pos is invalid\n"); return NULL; } i++; }
return p; }
int list_show(linklist H) { linklist p;
if (H == NULL) { printf("H is NULL\n"); return -1; }
p = H->next; while (p != NULL) { printf("%d ",p->data); p = p->next; }
return 0; }
int list_search(linklist H, data_t value) { if (H == NULL) { printf("H is NULL\n"); return -1; }
linklist p = H->next; int i = 0;
while (p != NULL) { if (p->data == value) { printf("search ok, pos is %d\n",i); } i++; p = p->next; }
return 0; }
linklist list_merge(linklist H1, linklist H2) { if (H1 == NULL || H2 == NULL) { printf("H1 or H2 is null\n"); return NULL; } linklist H3 = list_create();
linklist TempPtr1 = H1->next; int count1 = 0; while (TempPtr1 != NULL) { list_insert(H3, TempPtr1->data, count1); TempPtr1 = TempPtr1->next; count1++; }
linklist TempPtr2 = H2->next; int count2 = 0; while (TempPtr2 != NULL) { list_insert(H3, TempPtr2->data, count1 + count2); TempPtr2 = TempPtr2->next; count2++; }
return H3; }
linklist list_purge(linklist H1, linklist H2) { if (H1 == NULL || H2 == NULL) { printf("H1 or H2 is null\n"); return NULL; }
linklist H3 = list_create();
int is_common = 0;
linklist TempPtr1 = H1->next; while (TempPtr1 != NULL) { linklist TempPtr2 = H2->next; while (TempPtr2 != NULL) { if (TempPtr1->data == TempPtr2->data) { printf("common elements:%d\n",TempPtr2->data); is_common = 1; list_insert(H3, TempPtr2->data, 0); } TempPtr2 = TempPtr2->next; }
if (is_common == 0) { list_insert(H3, TempPtr1->data, 0); } TempPtr1 = TempPtr1->next; }
return H3; }
int list_delete(linklist H, int pos) { if (H == NULL) { printf("no such linklist\n"); return -1; }
if (pos == 1) { linklist TempPtr = H->next; free(H); H = TempPtr; return 0; }
linklist Prev_Node = list_get(H, pos - 1); if (Prev_Node == NULL || Prev_Node->next == NULL) { printf("invalid position\n"); return -1; }
linklist TempPtr = Prev_Node->next; Prev_Node->next = TempPtr->next; free(TempPtr);
return 0; }
int list_free(linklist H) { if (H == NULL) { printf("no such linklist\n"); return -1; }
linklist TempPtr = H;
while (H != NULL) { TempPtr = H; H = H->next; free(TempPtr); }
return 0; }
int list_reverse(linklist H) { if (H == NULL) { printf("no such linklist\n"); return -1; }
if (H->next == NULL) { printf("linklist is empty\n"); return -1; }
linklist p = H->next->next; H->next->next = NULL;
while (p != NULL) { linklist q = p; p = p->next; q->next = H->next; H->next = q; }
return 0; }
int list_getmax(linklist H) { if (H == NULL) { printf("no such linklist\n"); return -1; }
if (H->next == NULL) { printf("linklist is empty\n"); return -1; }
int sum_new, max = 0; linklist p = H->next; linklist max_p = H->next; int sum = p->data + p->next->data;
while (p != NULL && p->next != NULL) { sum_new = p->data + p->next->data; if (sum_new >= max) { max = sum_new; max_p = p; }
p = p->next; } return max; }
|
栈(Stack)
和微机原理中栈的原理相同
注意:
栈主要用于以下场景:
- 函数调用:使用栈来保存当前函数的局部变量、返回地址等信息。当函数调用另一个函数时,新的函数信息被压入栈中,函数执行完毕后会从栈中弹出,返回上一个函数。
- 撤销操作
- 递归
- 二叉树遍历
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
| #include <stdio.h> #include <stdlib.h> #include "linear_stack.h" #include <string.h>
sqstack *stack_create(int len) { if (len < 0) { printf("len is illegal\n"); return NULL; }
sqstack *ss;
ss = (sqstack *)malloc(sizeof(sqstack)); if (ss == NULL) { printf("Failed to allocate memory for stack.\n"); return NULL; }
ss->data = (data_t *)malloc(sizeof(data_t) * len); if (ss->data == NULL) { printf("Failed to allocate memory for stack data.\n"); free(ss); return NULL; }
memset(ss->data, 0, len*sizeof(data_t)); ss->top = -1; ss->maxlen = len;
return ss; }
int stack_push(sqstack *s, data_t value) { if (s == NULL) { printf("no such stack\n"); return -1; } if (s->top == s->maxlen - 1) { printf("stack is full\n"); return -1; }
s->top++; s->data[s->top] = value;
printf("stack push ok\n"); return 0; }
int stack_empty(sqstack *s) { if (s == NULL) { printf("no such stack\n"); return -2; }
if (s->top == -1) { printf("stack is empty\n"); return -1; } else { return 0; } }
int stack_full(sqstack *s) { if (s == NULL) { printf("no such stack\n"); return -2; }
if (s->top == s->maxlen - 1) { printf("stack is full\n"); return -1; } else { return 0; } }
data_t stack_pop(sqstack *s) { if (s == NULL) { printf("Stack not initialized\n"); return -999; }
if (s->top == -1) { printf("Stack is empty\n"); return -999; } data_t temp = s->data[s->top]; s->top--; return temp; }
int stack_clear(sqstack *s) { if (s == NULL) { printf("Stack not initialized\n"); return -1; }
if (s->top == -1) { return 0; }
s->top = -1;
return 0; }
int stack_free(sqstack *s) { if (s == NULL) { printf("Stack not initialized\n"); return -1; }
free(s->data); s->data = NULL; free(s);
return 0; }
|
队列(Queue)
基于数组的队列
等同于现实生活中的“排队”,元素从列尾存入,列首取出
双端队列:列首和列尾都可以进行存入和取出;或解释为:同一个队列共享列首和列尾
队列多被用于:
- 任务调度
- 消息队列
- IO请求管理(确保系统资源的有序访问)
队列结构体一般为
1 2 3 4 5 6 7
| typedef int data_t; #define N 64 typedef struct { data_t data[N]; int front, rear; }sequeue_t;
|
其中rear
一般指示队尾元素的下一个位置,也就是下一个要入队的元素位置。用于处理队列为空或只有一个元素时的情况。头尾位置重合时,队列即为空。
初始front, rear = 0;
- 出队列:
x = sq[front++]
- 入队列:
sq[rear++] = x;
当队列中前几个元素出队时,会造成这些位置为空,从而浪费队列中的可存储空间。此时可以使用循环队列。
为区别空队和满队,满队元素个数比数组元素个数少一个。
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
|
sequeue queue_create() { sequeue qq = (struct queue*)malloc(sizeof(struct queue));
if (qq == NULL) { printf("malloc failed\n"); return NULL; }
qq->front = 0; qq->rear = 0;
memset(qq->data, 0, sizeof(qq->data));
return qq; }
int queue_print(sequeue q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == q->rear) { printf("queue is empty"); return -1; }
for (int i = q->front; i < q->rear; i++) { printf("%d ", q->data[i]); }
return 0; }
int enqueue(sequeue q, data_t data) { if (q == NULL) { printf("no such queue\n"); return -1; }
if ((q->rear + 1) % N == q->front) { printf("queue is full\n"); return -1; }
q->data[q->rear] = data; q->rear = (q->rear + 1) % N;
return 0; }
int dequeue(sequeue q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == q->rear) { printf("queue is empty"); return -1; }
int temp = q->data[q->front]; q->front = (q->front + 1) % N;
return temp; }
int queue_clear(sequeue q) { if (q == NULL) { printf("no such queue\n"); return -1; }
q->front = 0; q->rear = 0;
return 0; }
|
基于链式存储的队列
插入操作在队尾进行,删除操作在队首进行,由队首指针和队尾指针控制队列操作
结构体需要定义两个:节点结构体 和 队列结构体
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef int data_t;
typedef struct node { data_t data; struct node* next; }*linklist;
typedef struct queue { linklist front; linklist rear; };
|
- 初始化:创建空队列,front和rear指针都为空
- 入队:在队尾新增一个节点,并更新当前队尾节点的next指针
- 出队:更新front指针到待出队节点的下一个节点,然后删除原有队首节点
与线性表不同的是,链式队列中rear指针一般指向最后队列的一个节点,而非最后一个节点的下一个节点。
判断链式队列是否为空的条件只有一个:front == NULL, rear == NULL
,不能使用front==rear
判断,因为这里front
和rear
都是指针,并且rear
指向的是最后一个节点,而非线性队列中的指向最后一个位置的下一个位置(待插入位置)。假设队伍中只有一个元素,这时候front
和rear
二者相同,但队列不为空。
创建新的链式队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
struct queue* queue_create() { struct queue* q = (struct queue*)malloc(sizeof(struct queue));
if (q == NULL) { printf("malloc failed\n"); return NULL; }
q->front = NULL; q->rear = NULL;
return q; }
|
- 为队列申请内存,暂时先不申请节点的,因为这是一个空队列,目前还没有元素
- 因为队列中没有节点,front和rear指针都为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
|
int queue_print(struct queue* q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == NULL) { printf("queue is empty\n"); return -1; }
struct node* temp = q->front;
while (temp != NULL) { printf("%d", temp->data); temp = temp->next; }
printf("\n");
return 0; }
|
- 在遍历时要用临时指针temp来代替front,如果直接用front指针的话,遍历一次之后front就回不去了,整个队列会被破坏
- 尽量不要在遍历前用temp存储front指针,用front指针遍历完之后再front = temp这种形式返回回去的这种方法,在多线程的时候可能会有问题
- 遍历结束的条件是当temp指针走到NULL之后,表明从队首节点到队尾节点全都扫描到了,而不是
temp(front)=rear
或者temp(front)=rear->next
,因为链式队列中rear指针指向的是最后一个(队尾)节点
其他
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
|
struct queue* queue_create() { struct queue* q = (struct queue*)malloc(sizeof(struct queue));
if (q == NULL) { printf("malloc failed\n"); return NULL; }
q->front = NULL; q->rear = NULL;
return q; }
int queue_print(struct queue* q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == NULL) { printf("queue is empty\n"); return -1; }
struct node* temp = q->front;
while (temp != NULL) { printf("%d ", temp->data); temp = temp->next; }
printf("\n");
return 0; }
int enqueue(struct queue* q, data_t val) { if (q == NULL) { printf("no such queue\n"); return -1; } struct node* newnode = (struct node*)malloc(sizeof(struct node));
newnode->data = val; newnode->next = NULL;
if (q->rear == NULL) { q->front = newnode; q->rear = newnode; } else { q->rear->next = newnode; q->rear = newnode; }
return 0; }
int dequeue(struct queue* q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == NULL) { printf("queue is empty\n"); return -1; }
struct node* temp_node = q->front; int temp = temp_node->data; q->front = q->front->next;
if (q->front == NULL) { q->rear = NULL; }
free(temp_node);
return temp; }
int queue_clear(struct queue* q) { if (q == NULL) { printf("no such queue\n"); return -1; }
if (q->front == NULL) { return 0; }
struct node* temp; while (q->front != NULL) { temp = q->front; q->front = q->front->next; free(temp); }
q->rear = NULL;
return 0; }
|
球钟问题(队列和栈的应用)
球钟问题(Ball Clock Problem)是一种经典的算法和数据结构题目,主要用于考察队列和栈的结合运用。问题的基本情景是:
假设有一个球钟系统,它通过小球来计时。系统中有 3 个轨道,每个轨道容量不同:
- 分钟轨道(Min Track):最多可容纳 4 个小球。
- 五分钟轨道(Five-Min Track):最多可容纳 11 个小球。
- 小时轨道(Hour Track):最多可容纳 11 个小球。
运作机制:
- 初始状态下有一组编号为 1 到 n 的小球,n 是球的总数,这些小球依次通过队列进行处理。
- 每分钟,第一个小球从队列中取出,放到分钟轨道上。
- 当分钟轨道满(即放入第5个小球时),轨道中的 4 个小球被依次放回队列,保持原来的顺序,而第 5 个小球则进入五分钟轨道。
- 当五分钟轨道满(即放入第12个小球时),轨道中的 11 个小球被依次放回队列,第 12 个小球进入小时轨道。
- 当小时轨道满(即放入第12个小球时),轨道中的 11 个小球被依次放回队列,第 12 个小球也回到队列的末尾。
问题的一个常见考点是计算在给定的初始状态下,经过多少分钟后,小球队列的顺序会恢复到最初的状态。
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
| #include <stdio.h> #include <stdlib.h> #include "queue.h" #include "linear_stack.h"
#define N 27
int main() { struct queue* main_queue = queue_create(); struct stack* minute = stack_create(4); struct stack* five_minute = stack_create(11); struct stack* hour = stack_create(11);
int time = 0;
for (int i = 1; i <= N; i++) { enqueue(main_queue, i); }
while (!stack_full(hour)) { while (!stack_full(five_minute)) { while (!stack_full(minute)) { int ball = dequeue(main_queue); stack_push(minute, ball); time++;
if (stack_full(minute)) { int count_min = 0; while (count_min < 4) { int min_top_ball = stack_pop(minute); enqueue(main_queue, min_top_ball); count_min++; }
stack_push(five_minute, ball); } } if (stack_full(five_minute)) { int count_five_min = 0; while (count_five_min < 11) { int five_min_top_ball = stack_pop(five_minute); enqueue(main_queue, five_min_top_ball); count_five_min++; }
stack_push(hour, dequeue(main_queue)); } }
if (stack_full(hour)) { int count_hour = 0; while (count_hour < 11) { int hour_top_ball = stack_pop(hour); enqueue(main_queue, hour_top_ball); count_hour++; } } }
printf("钟表恢复到初始状态所需时间: %d 分钟\n", time);
return 0; }
|