问题描述:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下1只猴子时,这个猴子就是猴王,编程要求输入n,m后,输出最后猴王的编号。
输入:每行连续的两个整数n,m,用一个空格分开,以0 0结束输入
输出:每行一个整数,代表猴王的编号
一.链表法
它这个环,正好能用上循环链表
所以用到的是链表的创建和链表的删除,然后这里用到的是尾插法(插入与原节点顺序相同)
/*构建循环链表*/
rear = head; //每次都要先让rear先指向head
for (int i = 0; i < n; i++) { //循环链表的创建
Link node;
node = (Link)malloc(sizeof(Node));
node->next = head->next;
node->data = i + 1; //猴子的编号
rear->next = node;
rear = node;
}
关键的几步:
tail->next = p;
p->next = head->next;
tail = p;
图解:
利用尾插法把所有的结点插入,形成一个循环链表
删除结点的操作
要引入两个结点,p表示当前结点,q表示p结点的前一个结点
先初始化p,q
p = head->next;
q = tail; //rear->next就是head->next,即q
图解:
如果找到了猴子大王,我们进行删除操作
if (k == m) { //删除该猴子
p->next = q->next;
free(q);
k = 1;
q = p->next;
m=q->password;
}
如果没找到
else {
p = q;
q = q->next;
k++;
}
图解:
易错点:
1. 因为连续的输入,所以每次开始创建新链表的时候都要定义rear=head;
2. 删除到只剩下两个有效结点的时候,如果删除第一个结点会使链表不完整连贯,所以结尾要加上head->next=p,把链表连起来
3.记得free(p)最后一个猴王的链表结点,还有头指针free(head);
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node *next;
} Node, *Link;
int main() {
int n, m;
int answer[100];//储存猴王的编号
int count = 0;
Link head, rear; //尾插法做准备
Link p, q;
head = (Link)malloc(sizeof(Node));
head->next = NULL;
rear = head;
while (1) { //保证连续的输入数据
scanf("%d %d", &n, &m);
if (n == 0 || m == 0) {
free(head);
break;//循环退出的条件
} else {
rear = head; //每次都要先让rear先指向head
for (int i = 0; i < n; i++) { //循环链表的创建
Link node;
node = (Link)malloc(sizeof(Node));
node->next = head->next;
node->data = i + 1; //猴子的编号
rear->next = node;
rear = node;
}
int k = 1; //记录猴子的报数
q = rear; //rear->next就是head->next,即q
p = head->next;
while (p != q) { //只剩下一个序号的时候,p,q指针重合
if (k == m) { //删除该猴子
q->next = p->next;
free(p);
k = 1;
p = q->next;
}//移动q到下一个有效结点上
else {
q = p;
p = p->next;
k++;
}
}
}
//删除到只剩下两个有效结点的时候,如果删除第一个结点会使链表不完整连贯
head->next = p;
answer[count] = p->data;
free(p);
count++;
}
for (int i = 0; i < count; i++) {
printf("%d\n", answer[i]);
}
free(head);
return 0;
}