约瑟夫环:设编号分别为1、2、3....、n的n个人围坐一圈,提前约定编号为k(1≤k≤n)的人从1开始计数,数到m的那个人淘汰出局,他的下一个人又从1开始计数,数到m的人再淘汰出局,依次类推,直到最后只剩一个人,他将获得胜利。
设n=8,k=3,m=4时,如下图
则淘汰序列为:6、2、7、4、3、5、1、8
最终存活者编号为8
算法思路:用头插法构建一张单向循环链表,结点内保存的数据即为编号。首先找到第k个结点,然后开始从1计数,计到m时删除该结点;然后从被删除结点的下一结点起又从1计数,直到最终只剩一个结点时循环结束,输出最终存活者编号。
以下为源代码,另外包含单向循环链表的一些基本操作和链表合并等
/*===============================================
* 文件名称:list.h
* 创 建 者:xm
* 创建日期:2022年07月29日
* 描 述:
================================================*/
#ifndef _LIST_
#define _LIST_
#include <stdio.h>
#include <stdlib.h>
typedef int data_t;
typedef struct linklist
{
data_t data;//数据域
struct linklist *next;//指针域
}LinkList;
//创建头节点
LinkList *Creat_Linklist();
//判空
int Linklist_Is_Empty(LinkList *h_node);
//求表长
int Linklist_length(LinkList *h_node);
//从表头插入数据
void Linklist_Insert_head(LinkList *h_node,data_t data);
//打印
void Linklist_Show(LinkList *h_node);
//两表合并
void lianjie(LinkList *linka,LinkList *linkb);
//约瑟夫环
void yuesefu(LinkList *head,int,int,int);
#endif
/*===============================================
* 文件名称:list.c
* 创 建 者:xm
* 创建日期:2022年07月29日
* 描 述:约瑟夫环
================================================*/
#include "list.h"
//创建头节点
LinkList *Creat_Linklist()
{
LinkList *head=(LinkList*)malloc(sizeof(LinkList));
if(NULL==head)
{
printf("malloc error!\n");
return NULL;
}
head->data=-1;
head->next=head;
return head;
}
//判空
int Linklist_Is_Empty(LinkList *h_node)
{
if(h_node->next==h_node)return 0;//表空返回0
else return 1;
}
//求表长,为有效数据节点个数(即除去头节点)
int Linklist_length(LinkList *h_node)
{
int len=0;
LinkList *p=h_node->next;//p指向头节点后的第一个节点
while(p!=h_node)
{
len++;
p=p->next;
}
return len;
}
//打印
void Linklist_Show(LinkList *h_node)
{
if(Linklist_Is_Empty(h_node)==0)
{
printf("表为空,无法打印\n");
return ;
}
LinkList *p=h_node->next;//p指向头节点后的第一个节点
while(p!=h_node)
{
printf("%d,",p->data);//打印数据
p=p->next;
}
puts("");
}
//头插法
void Linklist_Insert_head(LinkList *h_node,data_t data)
{
LinkList *p=(LinkList *)malloc(sizeof(LinkList));
if(NULL==p)
{
printf("malloc new node error!\n");
return ;
}
p->data=data;//存数据
p->next=h_node->next;//新节点拿到下一节点的地址
h_node->next=p; //上一节点刷新得到新节点的地址
}
//两链表的合并
void lianjie(LinkList *linka,LinkList *linkb)
{
LinkList *p,*q;
p=linka->next;
q=linkb->next;
while(p->next!=linka)
{
p=p->next;
}
while(q->next!=linkb)
{
q=q->next;
}
p->next=linkb->next;
q->next=linka;
linkb->next=linkb;
}
//约瑟夫环
void yuesefu(LinkList *head,int n,int k,int m)
{
LinkList *q=NULL;
Linklist_Insert_head(head,n);//先头插一个结点,即尾结点
q=head->next;//保存尾结点地址,用于后面头尾连接
while(--n)
{
Linklist_Insert_head(head,n);
}
Linklist_Show(head);//打印编号
q->next=head->next;//最后一个节点连上第一个节点
q=head->next;//重置q,用于后面指向起始结点
free(head);//释放头结点
while(--k)
{
q=q->next;
}
printf("起始结点编号=%d\n",q->data);
LinkList *temp=NULL;//用于删除目标结点
int count;//用于每轮计数
while(q->next!=q)
{
count=m-1;
while(--count)
{
q=q->next;
}
temp=q->next;//指向目标结点
printf("%d,",temp->data);//打印淘汰者编号
q->next=temp->next;//删除目标节点
free(temp);//释放目标节点
q=q->next;//指向新起始节点
}
printf("\n最后存活者编号%d\n",q->data);
}
/*===============================================
* 文件名称:main.c
* 创 建 者:
* 创建日期:2022年07月29日
* 描 述:
================================================*/
#include "list.h"
int main(int argc, char *argv[])
{
//约瑟夫环
int n,k,m;
printf("请输入人数 起始序号 次数\n");
scanf("%d%d%d",&n,&k,&m);
printf("n=%d,k=%d,m=%d\n",n,k,m);
LinkList *head=Creat_Linklist();
yuesefu(head,n,k,m);
return 0;
}