C语言通过递归方式实现约瑟夫环问题

    约瑟夫环是算法的一个经典问题,题目大意是N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。在算法中可以用一个循环链表来模拟人围成的圈,链表的每一个节点的数据域分别存放这个节点的 id 以及密码(即每个人报的数),链表首先按照系统的初始密码进行遍历循环,找到第一个节点拿到其密码然后删除之,在通过拿到的密码进行下一次遍历循环,直到链表中只剩下最后一个节点。
    了解过递归的都知道,一个程序要想实现递归需要有两个条件,一是原问题要能转化成若干个子问题,并且子问题须于原问题求解方式一致; 二是递归必须有一个出口,即程序递归调用到最后必须有一个结束的终点来结束它。 仔细分析题目可以知道,算法的主要步骤为:遍历节点,找到密码对应的目标节点,删除目标节点;然后继续遍历,删除目标节点…直到只剩下最后一个节点。 所以通过分析可知,此问题符合递归的两个基本条件。故可以用递归的方法解决。
    代码如下:

#include <stdio.h>
#include <malloc.h>

typedef struct LinkList {
	int id;
	int password;
	struct LinkList *next;
}LinkList;	//定义链表节点

LinkList *create(int n) 
{
	struct LinkList *head, *p, *tail;
	int i,d = 1;
	head = (struct LinkList * )malloc(sizeof(struct LinkList));
	p = tail = (struct LinkList * )malloc(sizeof(struct LinkList));
	head ->next = p;
	for(i = 0; i < n; i++) 
	{	
		p -> id = d ++;
		scanf("%d",&p -> password);
		if(i == 0)
		{
			head->next = p;
		}
		else
		{
			tail->next = p;
		}
		tail = p;
		p = (struct LinkList * )malloc(sizeof(struct LinkList));
	}
	tail ->next = head->next;	
	free(p);
	return(tail);	//尾插法建立循环链表,注意链表返回的是尾节点
}

find(struct LinkList *tail,int n, int firstpassword) 
{
	struct LinkList *pre, *p;
	int newpassword;
	pre = tail;
	p = tail->next;
	while(-- firstpassword){
		pre = p;
		p = p->next;
	}
	pre->next = p->next;
    newpassword = p->password;
    printf("%d ",p->id);
    p = p->next;
    if (n == 1) {
    	return;
	}
    find(pre,--n,newpassword);
}

main() {
	int n,first;
	struct LinkList *tail;
	printf("请输入节点数:");
	scanf("%d",&n);
	printf("请输入初始密码:");
	scanf("%d",&first);
	printf("请顺序输入节点密码:");
	tail = create(n);
	find(tail,n,first);
}

    主要就程序的find()函数部分做一下简要说明:

  1. 定义函数的参数为初始链表的尾指针*tail,链表的节点数n,初始密码firstpassword。
  2. 定义指针*pre,*p,做节点的遍历与删除用,定义newpassword用以存放每一次的新密码。
  3. 首先让指针pre指向链表尾节点,p指向尾节点的下一个节点,即p指向了循环链表的额头节点。
  4. 用while语句进行循环,password=0时循环结束(注意要先对password进行自减,具体原因可以自己在纸上画一下),循环时p进行链表的遍历,而pre始终指向p。
  5. 循环结束后,p指向的节点即为需要删除的目标节点,拿到密码输出目标节点的 id 随后正常进行删除即可,然后p指向目标节点的下一个节点,p继续指向目标节点。此处进行一个判断,如果链表的节点 n 数为一的话,说明已经到递归的终点了,就结束程序。
  6. 如若没到终点,便进行下一次递归,即函数调用自身,不过新调用是指针为pre,而且节点数自减,密码为新密码。

    随机的一组测试结果如下:

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值