案例: 约瑟夫问题 (问题 → 思路 → 代码)
程序: 维护零件数据库 (改进版)
课堂编码与小测
n 个人围成一圈 (编号 1~n), 从 1 号开始报数, 报到 m 的人出列; 下一个人继续从 1 报起, 直到只剩一人
样例: 输入 7 3 → 出列顺序 3 6 2 7 5 1 4, 幸存者 4
现场演示: 用链表把这个问题写出来
提示: 围成一圈 = 尾结点的 next 指向头结点 (循环链表); 出列 = 删除结点
⚡ 在 VS Code 中打开 joseph.cprev->next = prev->next->next 绕过 → free 回收
prev->next == prev (只剩一人)
/* 约瑟夫问题: n 个人围圈, 报到 m 出列 */
#include <stdio.h>
#include <stdlib.h>
struct node {
int no; /* 编号 */
struct node *next;
};
int main(void)
{
int n, m;
struct node *first = NULL, *p, *prev;
if (scanf("%d %d", &n, &m) != 2 || n < 1) {
printf("输入无效\n");
return 1;
}
for (int i = n; i >= 1; i--) { /* 头插法建 1..n */
p = malloc(sizeof(struct node));
if (p == NULL) { /* 分配失败 */
printf("内存不足\n");
return 1;
}
p->no = i;
p->next = first;
first = p;
}
prev = first; /* 找尾结点, 连成环 */
while (prev->next != NULL) {
prev = prev->next;
}
prev->next = first;
while (prev->next != prev) { /* 只剩一人时停止 */
for (int i = 1; i < m; i++) { /* 报数 m-1 步 */
prev = prev->next;
}
p = prev->next; /* 报到 m 的人 */
printf("%d ", p->no);
prev->next = p->next; /* 绕过 p */
free(p); /* 回收 */
}
printf("\n幸存者: %d\n", prev->no);
return 0;
}
建环用头插法 (倒着插, 正着报); 尾结点怎么找? 从 first 走到 next == NULL
出列后 prev 不要动——它正好是下一个报数者的前一个
#define NAME_LEN 25
struct part {
int number;
char name[NAME_LEN+1];
int on_hand;
struct part *next;
};
inventory2.c 是对上一节 13-1 零件数据库程序的改进, 这次数据库存储在一个链表中.
使用链表的优点:
无需限制数据库的大小.
数据库可以很容易地按零件编号排序.
在原始程序中, 数据库没有排序.
part 结构体多加一个成员: 指向下一个结点的指针:
inventory 将指向链表首结点:
struct part *inventory = NULL;
新程序中的大多数函数与原始程序中的版本非常相似.
find_part 和 insert 会更复杂, 因为要按零件编号对链表 inventory 中的结点排序; 这两个函数在课堂编码中完成.
现场演示
1 → 2 → 3 变成 3 → 2 → 1 (三指针: prev、cur、next)
提示: 反转时先把 next 存下来, 再改方向——顺序错了链表就断了
⚡ 在 VS Code 中打开 skeleton.cprev->next != prev 是什么值?循环还会继续吗?
prev->next = p->next, 忘了 free(p) 会怎样?