约瑟夫环问题(C语言数组和循环链表)

本文将用两种方式(数组和循环链表)求解约瑟夫环问题,为了更好理解,本文将从洛谷的P1996 约瑟夫问题出发。

题目描述

n个人围成一圈,从第一个人开始报数,数到 m的人出列,再由下一个人重新从1开始报数,数到 m的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式

输入两个整数 n,m。

输出格式

输出一行 n个整数,按顺序输出每个出圈人的编号。

输入输出样例

输入

10 3

输出

3 6 9 2 7 1 8 5 10 4

说明/提示

1≤m,n≤100

方法一(数组):

解题思路:创建一个大小为n的数组用来表示n个人,同时用给数组元素赋值0或1表示该人的生死状态(0为生,1为死),同时设置一个flag用来表示当前淘汰的人数,当淘汰人数为n-1个人就跳出循环。

代码实现:

#include <stdio.h>
int main() {
	int n, q, i = 0, count = 1, flag = 0;
	printf("人数:");
	scanf("%d", &n);//输入人数
	getchar();
	printf("报到哪个数被杀死:");
	scanf("%d", &q);//输入报到哪个数死
	int a[n];
	for (i = 0; i < n; i++) {//给每个人设置“生”的状态
		a[i] = 0;
	}
	i = 0;
	printf("杀死顺序(下标):");
	while (1) {
		if (flag == n - 1) break;//当只剩下一个人时,跳出循环
		if (a[i] == 1) i++;//如果该人为死亡的状态,则跳过该人
		else {
			if (count == q) {//满足被杀死的条件
				a[i] = 1;//设置死亡状态
				flag++;//死亡人数加一
				printf("%d ", i);//输出这一次死亡的人的编号
				count = 1;//重置报数
				i++;//移动到下一个人开始
			} else {//不满足被杀死的状态
				count++;//继续报数
				i++;
			}
		}
		if (i == n) i = 0;//由于数组下标从0开始,所以当i=n时,应该是移动到第一个人了
	}
	printf("\n存活(下标):");
	for (i = 0;; i++) {
		if (a[i] != 1) {//输出最后存活的人
			printf("%d", i);
			break;
		}
	}
	return 0;
}

该方法一般为约瑟夫环问题的常见方法,但时间复杂度较高,而下面的方法可以帮助我们更好的理解循环链表的结构。

方法二(循环链表):

解题思路:

创建一个循环链表,这样可以看成一群人围成一圈的状态,并在头结点开始给人从1开始赋值表示编号,另外创建一个大小为n的数组用来储存死亡顺序(编号),每当有人死亡时,就把该人的编号储存到数组中,同时把该人从链表中删去。还需设计一个flag来表示死亡人数,当死亡人数为n-1人时就跳出循环。

代码实现:

创建循环链表函数Creat:

LINK* Creat(int n) {//该函数用来创建一个循环链表并返回该链表的尾结点
	LINK *head = NULL,*p = NULL, *pr = NULL;//head用来表示头节点,p用来申请内存空间,pr用来表示当前链表的尾结点
	int i;
	for(i = 1;i <= n;i++)//从1开始创建n个链表结点并依次赋值为i
	{
		p = (LINK*)malloc(sizeof(LINK));//申请动态内存
		if(p == NULL)
		{
			printf("ERROR");
			return NULL;
		}
		p->id = i;
		if(head == NULL)//如果头节点为空指针,则把p赋给head
		{
			head = p;
		} else {//否则就用尾插法创建链表
			pr->next = p;
		}
		pr = p;//将pr赋值为指向当前链表最后一个结点
	}
	pr->next = head;//将最后一个节点的next指向head以构成循环链表
	return pr;//返回尾结点
}

用于确定出圈顺序的函数YueSeFu:

void YueSeFu(LINK* tail, int n, int m, int* a) {
	int count = 0,  flag = 0;//设置报数值和死亡人数值
	LINK* p = tail->next, *q = tail;//定义p为头指针,q为p前一个结点的指针
	while (flag < n - 1) {//死亡人数没有达到n-1
		count++;//报数
		if (count == m) {//满足出圈条件
            *a = p->id;//记录当前出圈者的编号
            a++;//让数组移向下一个值
			q->next = p->next;//让p的上一个结点的指针指向p的下一个结点
			free(p);//释放当前结点
			p = q->next;//p移动到下一个人
			count = 0;//重新开始报数
			flag++;//死亡人数加一
		} else {//不满足就移向下一个人
			q = p;
			p = p->next;
		}
	}
	*a = p->id;//记录最后一个人的编号
	free(p);
}

完整代码:

#include <stdio.h>
#include <stdlib.h>
typedef struct link {
	int id;
	struct link* next;
} LINK;

LINK* Creat(int n);
void YueSeFu(LINK* head, int n, int m, int* a);
void DisplyLINK (LINK *head);

int main(void) {
	int n, m;//n为总人数,m为出圈时报的数
	scanf("%d%d", &n, &m);
	int a[n];
	LINK* tail = NULL;//定义尾指针
	tail = Creat(n);//创建一个循环链表并得到尾指针
	YueSeFu(tail, n, m, a);
	for (int i = 0; i < n; i++) {//打印出圈顺序(编号)
		printf("%d ", a[i]);
	}
}

LINK* Creat(int n) {
	LINK *head = NULL,*p = NULL, *pr = NULL;
	int i;
	for(i = 1;i <= n;i++)
	{
		p = (LINK*)malloc(sizeof(LINK));
		if(p == NULL)
		{
			printf("动态内存分配失败");
			return NULL;
		}
		p->id = i;
		if(head == NULL)
		{
			head = p;
		}
		else
		{
			pr->next = p;
		}
		pr = p;
	}
	p->next = head;
	return pr;
}

void YueSeFu(LINK* tail, int n, int m, int* a) {
	int count = 0, sum = 0;
	LINK* p = tail->next, *q = tail;
	while (sum < n - 1) {
		count++;
		if (count == m) {
			q->next = p->next;
			*a = p->id;
			free(p);
			p = q->next;
			count = 0;
			sum++;
			a++;
		} else {
			q=p;
			p=p->next;
		}
	}
	*a = p->id;
	free(p);
}


版权声明:本文为m0_61960789原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。