4. 双向约瑟夫问题

成绩10开启时间2021年09月17日 星期五 18:00
折扣0.8折扣时间2021年09月30日 星期四 23:55
允许迟交关闭时间2021年10月17日 星期日 23:55

  我们不妨将经典的约瑟夫问题进行扩展,变成一个双向的约瑟夫问题。

  已知 n 个人(不妨分别以编号 1,2,3,...,n 代表 )围坐在一张圆桌周围,首先从编号为 k 的人从 1 开始顺时针报数,1, 2, 3, ...,记下顺时针数到 m 的那个人,同时从编号为 k 的人开始逆时针报数,1, 2, 3, ...,数到 m 后,两个人同时出列。然后从出列的下一个人又从 1 开始继续进行双向报数,数到 m 的那两个人同时出列,...;。依此重复下去,直到圆桌周围的人全部出列。直到圆桌周围只剩一个人为止。

   如果双向报数报到 m 时落在同一个人身上,那本次出列的只有一个人。

  例如:5,1,2。则总共5个人,从1开始顺时针报数,数到2,定位编号2;同时从1开始报数数到2,定位编号5;2和5同时出列。然后继续开始报数,顺时针报数3,4,定位到4;逆时针报数4,3,定位3;4和3同时出列。最后剩余的为编号1。输出为:2-5,4-3,1,。

  如果输入:6,2,3。则输出:4-6,2,1-3,5,。其中第2次只输出一个2,表示第二次双向报数时,恰好都落在编号2上,所以只有一个编号出列。

输入:

n,k,m

输出:

按照出列的顺序依次输出编号。同时出列编号中间用减号"-”连接。

非法输入的对应输出如下

a)

输入:n、k、m任一个为0
输出:n,m,k must bigger than 0.

b)

输入:k>n
输出:k should not bigger than n.

测试输入期待的输出时间限制内存限制额外进程
测试用例 1以文本方式显示
  1. 1,0,0↵
以文本方式显示
  1. n,m,k must bigger than 0.↵
1秒64M0
测试用例 2以文本方式显示
  1. 1,2,1↵
以文本方式显示
  1. k should not bigger than n.↵
1秒64M0
测试用例 3以文本方式显示
  1. 5,1,2↵
以文本方式显示
  1. 2-5,4-3,1,↵
1秒64M0
测试用例 4以文本方式显示
  1. 6,2,3↵
以文本方式显示
  1. 4-6,2,1-3,5,↵
1秒64M0

代码

有两份代码,一份是直接从学长那里Ctrl+c+v的,毕竟当时实在太懒且水平太菜……另一份是我后来自己写的,用测试用例都能过,不过由于提交已经关了,没法验证这份代码能不能全部AC。学弟学妹们可以帮忙试试我自己写的代码,要是过不了就用学长的吧……反之都是一些阴间用例的问题。

学长代码

#include"stdio.h"  
#include"stdlib.h"  
  
struct man{  
    int id;  
    struct man *nextx;  
    struct man *nexty;  
};  
typedef struct man ysf;  
  
int main(){  
    int n,k,m;  
    scanf("%d,%d,%d",&n,&k,&m);  
      
    if(n<1||k<1||m<1){    
        printf("n,m,k must bigger than 0.\n");    
    }    
        
    else if(k>n){    
        printf("k should not bigger than n.\n");      
    }   
      
    else{  
    ysf *head,*p0;  
    p0=(ysf*)malloc(sizeof(ysf));  
    head=p0;  
    p0->id=1;  
      
    ysf *p,*p1,*p2;  
    int i;  
    for(i=2;i<=n;i++){  
        p=(ysf*)malloc(sizeof(ysf));  
        p->id=i;  
        p0->nextx=p;  
        p0=p0->nextx;  
        p->nextx=head;  
    }  
      
    p=head;  
    p0=p->nextx;  
      
    for(i=0;i<n;i++){  
        p0->nexty=p;  
        p0=p0->nextx;  
        p=p->nextx;  
    }  
      
    int total=n;  
    p=head;  
  
    for(i=1;i<k;i++){  
        p=p->nextx;  
    }  
      
    p1=p;  
    p2=p;  
      
    while(total>0){  
        for(i=1;i<m;i++){  
            p1=p1->nextx;  
            p2=p2->nexty;  
        }  
          
        if(p1->id==p2->id){  
            total--;  
            printf("%d,",p1->id);  
              
            ysf *p10,*p20;  
            p10=p1->nexty;  
            p20=p2->nextx;  
              
            p10->nextx=p20;  
            p20->nexty=p10;  
              
            p2=p10;  
            p1=p20;  
        }  
          
        else{  
            total-=2;  
            printf("%d-%d,",p1->id,p2->id);  
              
            ysf *p10,*p20;  
            p10=p1->nexty;  
            p10->nextx=p1->nextx;  
            p10->nextx->nexty=p10;  
            p1=p10->nextx;  
              
            p20=p2->nextx;  
            p20->nexty=p2->nexty;  
            p20->nexty->nextx=p20;  
            p2=p20->nexty;  
              
        }  
    }  
    printf("\n");         
    }  
}

我自己的代码

#include"stdio.h"
#include"stdlib.h"
typedef struct ysf{//双向链表 
	int id;
	struct ysf *left;
	struct ysf *right;
}human;

int main(){//模拟 
	int n,k,m;
	scanf("%d,%d,%d",&n,&k,&m);
	int total=n;
	
	if(n==0||k==0||m==0){
		printf("n,m,k must bigger than 0.\n");
		return 0;
	} 
	if(k>n){
		printf("k should not bigger than n.\n");
		return 0;
	}
	
	human *head=(human*)malloc(sizeof(human));
	int start=1;
	head->id=start;
	head->left=head;
	head->right=head;
	human *cur=head;
	while(--n){
		human *p=(human*)malloc(sizeof(human));
		p->id=++start;
		
		cur->right=p;
		p->left=cur;
		p->right=head;
		cur=cur->right;
	}
	head->left=cur;
//	while(head!=NULL&&total--){
//		printf("%d ",head->id);
//		head=head->right;
//	}
	
	human *q=head;
	while(q->id!=k){
		q=q->right;
	}
	
	human *baoshu1=q;
	human *baoshu2=q;
	
	while(total){
		for(int i=1;i<m;i++){
			baoshu1=baoshu1->left;
			baoshu2=baoshu2->right;
		}
		if(baoshu1->id!=baoshu2->id){
			printf("%d-%d,",baoshu2->id,baoshu1->id);
			total-=2;
			human *tmp1=baoshu1->right;
			tmp1->left=baoshu1->left;
			baoshu1=tmp1->left;
			baoshu1->right=tmp1;
		
			human *tmp2=baoshu2->left;
			tmp2->right=baoshu2->right;
			baoshu2=tmp2->right;
			baoshu2->left=tmp2;
		}
		else{
			printf("%d,",baoshu1->id);
			total--;
			baoshu1=baoshu1->left;
			baoshu2=baoshu2->right;
			baoshu1->right=baoshu2;
			baoshu2->left=baoshu1;
		}
	}
	printf("\n");
}

解释

这里还是用我的垃圾代码来解释……

首先,这题的方法基本上就是双向链表,其实很好理解,一般单链表的结构体只有一个指针,指向下一个结构体,我们这里的结构体有两个指针,一个指向上一个结构体,一个指向下一个结构体:

为什么要用双向链表呢,因为这题要同时向两个方向报数,因此我们要同时使用两个指针。其实我觉得用单链表也是可以的,就是每次报数的时候利用n,k,m计算一下两个报数者的编号,但是用单链表的话不好把报完数的人弹出链表,所以还是老老实实用双向链表吧。

双向链表的定义

很简单,相比单链表加一个指针就行:

typedef struct ysf{//双向链表 
	int id;
	struct ysf *left;
	struct ysf *right;
}human;

数据处理

然后来到主函数,把输入处理一下:(我觉得这一步还是挺重要的,考试万一不会写还能得几分)

	int n,k,m;
	scanf("%d,%d,%d",&n,&k,&m);
	int total=n;//可以优化一下,不用这个额外变量,不过懒得搞了 
	
	if(n==0||k==0||m==0){
		printf("n,m,k must bigger than 0.\n");
		return 0;
	} 
	if(k>n){
		printf("k should not bigger than n.\n");
		return 0;
	}

创建链表

根据我们输入的n,我们就可以创建一个链表来表示题目中“坐在圆桌周围的人”了

	human *head=(human*)malloc(sizeof(human)); //头结点
	int start=1;
	head->id=start;
	head->left=head;//一开始只有一个头结点,所以其左右的结点都是它自己 
	head->right=head;
	human *cur=head;
//	cur是current node,当前节点,先让它指向头结点,再让它往后移动 
	
	while(--n){ //可以用for,省一个total变量 
		human *p=(human*)malloc(sizeof(human));//新插入的结点 
		p->id=++start;//编号 
		
		cur->right=p;//当前结点右指针指向新结点 
		p->left=cur;//新结点左指针指向当前结点 
		p->right=head;
		//因为是循环链表,且新的结点都是最后的结点,
		//所以新结点的右边一定是头结点 
		cur=cur->right;//指向当前结点的指针往右移动,指向新的结点 
	}
	head->left=cur;
	//最后cur来到最后一个结点,这时候头结点的左指针还指着头结点呢
	//让头结点的左指针指向最后一个结点 

我们可以用下面的代码验证一下是不是生成了我们需要的链表:

	while(head!=NULL&&total--){
		printf("%d ",head->id);
		head=head->right;
	}

报数过程

首先,让指针来到编号为k的人那里:

	human *q=head;
	while(q->id!=k){
		q=q->right;
	}

然后,定义两个新指针,往左报数的人和往右报数的人

	human *baoshu1=q;
	human *baoshu2=q;

开始报数,直到人都报完了:

	while(total){
		for(int i=1;i<m;i++){
			baoshu1=baoshu1->left;//往左数m个人 
			baoshu2=baoshu2->right;//往右数m个人 
		} 
		if(baoshu1->id!=baoshu2->id){//2个人报数 
			printf("%d-%d,",baoshu2->id,baoshu1->id);
			total-=2;//总数少两个人 
			human *tmp1=baoshu1->right;//把报数的人踢出去,baoshu1指向踢出去人的左边 
			tmp1->left=baoshu1->left;
			baoshu1=tmp1->left;
			baoshu1->right=tmp1;
		
			human *tmp2=baoshu2->left;//把报数的人踢出去,baoshu2指向踢出去人的右边 
			tmp2->right=baoshu2->right;
			baoshu2=tmp2->right;
			baoshu2->left=tmp2;
			
		}
		else{//1个人报数 
			printf("%d,",baoshu1->id);
			total--;//总数少一个人 
			baoshu1=baoshu1->left;//同上,把报完数的人踢出去,指针指向新的位置 
			baoshu2=baoshu2->right;
			baoshu1->right=baoshu2;
			baoshu2->left=baoshu1;
		}
	}

最后注意一下乐学的格式:

	printf("\n");

图解(看懂了请跳过)

注释尽量写的很详细,如果还不太清楚可以看下面的图解:

头结点初始化

	human *head=(human*)malloc(sizeof(human)); //头结点
	int start=1;
	head->id=start;
	head->left=head;//一开始只有一个头结点,所以其左右的结点都是它自己 
	head->right=head;
	human *cur=head;

插入结点

加入编号为2的结点:

		human *p=(human*)malloc(sizeof(human));//新插入的结点 
		p->id=++start;//编号 
		
		cur->right=p;//当前结点右指针指向新结点 
		p->left=cur;//新结点左指针指向当前结点 
		p->right=head;
		//因为是循环链表,且新的结点都是最后的结点,
		//所以新结点的右边一定是头结点 

cur指向新结点,开始下一轮的循环:

cur=cur->right;//指向当前结点的指针往右移动,指向新的结点 

 加入编号为3的结点: 

以此类推……

整个过程可以用一个循环表示:

	while(--n){ //可以用for,省一个total变量 
		human *p=(human*)malloc(sizeof(human));//新插入的结点 
		p->id=++start;//编号 
		
		cur->right=p;//当前结点右指针指向新结点 
		p->left=cur;//新结点左指针指向当前结点 
		p->right=head;
		//因为是循环链表,且新的结点都是最后的结点,
		//所以新结点的右边一定是头结点 
		cur=cur->right;//指向当前结点的指针往右移动,指向新的结点 
	}

 最后,由于头结点的左指针还是指向它自己,因此要加上这么一句:

	head->left=cur;

到此为止,链表就构建完了,开始报数了。

把报数的人踢出去

写了好久了,有点累了,就画画踢人的过程吧,其他的都很好理解,我就不管了,其实我觉得这个画画的过程属实有点蠢,但保不准有零基础的同学看不懂代码(比如说大二的我),还是加上吧:

先找到报数者的右边的人(另外一个报数者是找左边,这里只讨论向左报数的情况)

			human *tmp1=baoshu1->right;//把报数的人踢出去,baoshu1指向踢出去人的左边 

 让报数者右边的左指针直接跳过报数者,指向报数者的左边:

			tmp1->left=baoshu1->left;

 报数者踢出去后,下一个报数的人是报数者的左边的人,因此让baoshu1指针提前指向左边的人,省的被连带着一块踢出去:

			baoshu1=tmp1->left;

 

 最后,让新的报数者的右指针指向tmp,使得没有指针指向原先的报数者,这样原先的报数者就等同于被踢出了这个圈子,其实最后还应该加上个free,不过也懒得加了:

 (如果有free的话:)

最后

整段代码有很多可以优化的地方,你们老师要是查重的话可以优化这些部分

希望小白还是花一些时间把其他人的代码好好看懂了,最好自己写出来,不要跟我一样到了大二下学期才开始补基础……

共勉

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值