链表作业2:约瑟夫环问题(猴子选大王)

问题描述:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下1只猴子时,这个猴子就是猴王,编程要求输入n,m后,输出最后猴王的编号。

输入:每行连续的两个整数n,m,用一个空格分开,以0 0结束输入

输出:每行一个整数,代表猴王的编号

一,链表法

方法:1.链表的创建(尾插法)

            2.链表的删除

易错点:

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; //记录猴子的报数
			p = rear; //rear->next就是head->next,即q
			q = head->next;
			while (p != q) { //只剩下一个序号的时候,p,q指针重合
				if (k == m) { //删除该猴子
					p->next = q->next;
					free(q);
					k = 1;
					q = p->next;
				}//移动q到下一个有效结点上
				else {
					p = q;
					q = q->next;
					k++;
				}
			}
		}
		//删除到只剩下两个有效结点的时候,如果删除第一个结点会使链表不完整连贯
		head->next = q;
		answer[count] = q->data;
		free(q);
		count++;
	}
	for (int i = 0; i < count; i++) {
		printf("%d\n", answer[i]);
	}
	free(head);
	return 0;
}

二,用数组模拟链表法

(这里是 懒猫老师《数据结构》课程的该题讲解视频的截图,讲的非常清晰!!)

 

 方法:

1.数组的下标就是猴子的编号-1,数组里对应存的内容就是 连接关系(相当于指针域)

2.关键就是将数组末尾的数据设置为0,相当于建成了一个循环链表

完整代码:

#include <stdio.h>

int main() {
	int n, m;
	while (1) {
		scanf("%d %d", &n, &m);
		if (n == 0 || m == 0) {
			return 0;
		} else {
			int pos = 0;
			int prior = n - 1;
			int number = n;
			int monkey[n];
			int i = 0;
			for (i = 0; i < n - 1; i++) { //相当于创建数组链表
				monkey[i] = i + 1;
			}
			monkey[i] = 0; //循环链表
			int k = 1; //记录猴子报数
			while (number > 1) { //或者写成pos!=prior
				if (k == m) {
					monkey[prior] = monkey[pos];
					monkey[pos] = -1;
					pos = monkey[prior]; //pos移动到下一个位置
					number--;
					k = 1;
				} else {
					prior = pos;
					pos = monkey[pos];
					k++;
				}
			}
			for (int i = 0; i < n; i++) {
				if (monkey[i] != -1)
					printf("%d\n", i + 1);
			}
		}
	}
	return 0;
}

 三,普通的数组法

方法:思路与第二个方法不同,在普通的数组里构建出循环的效果,我们可以使用以下式子来代替pos++;

                                          pos=(pos+1)%n    (n是总共的数字个数)

易错点:要给monkey数组大小设置成题目n能给到的最大值,因为要输入的数据的组数是未知的,即每组n(猴子的个数)都可能是不同的。

#include <stdio.h>

int main() {
	int n, m;
	while (1) {
		scanf("%d %d", &n, &m);
		if (m == 0 || n == 0)
			return 0;
		int monkey[301] = {0};
		for (int i = 0; i < n; i++) {
			monkey[i] = i + 1;
		}
		int count = 1;
		int number = n;
		int pos = 0;
		while (number > 1) {
			if (monkey[pos] > 0) {//判断该位置有没有数据
				if (count == m) {
					monkey[pos] = 0;
					count = 1;
					number--;
					pos = (pos + 1) % n; //i++;
				} else {
					pos = (pos + 1) % n; //i++;
					count++;
				}
			} else//没有数据就++
				pos = (pos + 1) % n;
		}
		for (int i = 0; i < n; i++) {
			if (monkey[i] != 0)
				printf("%d\n", monkey[i]);
		}
	}
	return 0;
}

(内容有错误的话欢迎指正喔!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想写好代码的小猫头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值