关于报数出队问题

在上周的总结中,好像提了一个问题。

有N个人围成一圈,第m个人的序号是m.从第1个人开始顺序报号1、2、3、4、......, 凡报到3的倍数的人出圈。请计算最后留在圈子里的人的序号。
(N <= 10000).

后来想了一下这个问题好像有点僵化,于是又做了一些改动。

有N个人围成一圈,第m个人的序号是m.从第1个人开始顺序报号1、2、3、4、......, 凡报到K的倍数的人出圈。请计算最后留在圈子里的人的序号。

(N <= 10000, K <= 10000).

对于这个问题,有很多解决问题的方案,不妨来看看。

数组标记

这个思路很好理解。我们用一个数组标记第i个人有没有出队,每当走过K个没有被标记的人时,就使当前的人出队,直到剩下一个人。如果到达了最后一个人,便从第一个人继续计数。

点击查看代码
#include<stdio.h>
#include<string.h> 
#define N 10010;

int st[N];//st数组中0表示未出列, 1表示已出列

int main(){
	int n, k;
	scanf("%d%d", &n, &k);
	int num = n, per = 1;//num表示还有多少人,per表示走到了第几个人 
	while(num > 1){
		int t = 0;//t表示这次报数中已经有几个人报数 
		while(t < k){
			if(!st[per]){
				per ++;
				t ++; 
			}
			else{
				per ++;
			}
			if(per == n + 1)per = 1;//如果走到了最后一个人便回到开头 
		}
		st[per] = 1;//表示第per个人出列
		num --; 
	}
	for(int i = 1;i <= n;i ++)
		if(!st[i]){
			printf("%d", i);
			break;
		}
	return 0;
} 

数组的删除是非常麻烦的,需要把某一个位置的元素删除,然后其余之后的元素全都向前移动。
在上述方法中,用了一个数组标记的方法来体现成员是否已经出列,可以说是比较巧妙了。

链表实现成员出入

链表下的删除是比较简单的,只需把前一项的指针指向后一项的地址,并释放该项的内存即可。

点击查看代码
#include<stdio.h>
#include<stdlib.h>

typedef struct person{
	int data;
	struct person *next;
}PERSON;

int main(){
	int n, k;
	scanf("%d%d", &n, &k);
	PERSON *head = NULL, *p = NULL, *q = NULL;
	for(int i = 1;i <= n;i ++){
		p = (PERSON*)malloc(sizeof(PERSON));
		p -> data = i;
		p -> next = NULL;
		if(head == NULL){
			head = p;
			q = head;
		}
		else{
			q -> next = p;
			q = q -> next;
		}
	}
	q -> next = head;
	PERSON *t = head;
	while(t -> next != t){
		for(int i = 1;i < k - 1;i ++)t = t -> next;//从当前人向后走过k - 2个人 
		PERSON *out = t -> next;//out是第k个人 
		PERSON *ne = out -> next;//ne是第k + 1个人 
		t -> next = ne;//让第k - 1个人下面的人变为第k + 1个人 
		t = t -> next;// 走向下次报数的第一个人 
		free(out);
	}
	printf("%d", t -> data);
	return 0;
}

想起来好久没写链表了,写链表是真的麻烦。这种链表的形式无疑是最形象描述出列的方式。
不过做题的时候个人不喜欢写链表,因为写链表真的麻烦,而且指针空间操作也比较消耗时间。

模拟队列

这是个人最喜欢的做法。一方面这个做法好写,另一方面这种算法确确实实运用到了队列FIFO的性质。

把i个人看成正在排队的i个人,如果排到队头的人的队号是k的倍数,就让他出队,否则让他继续在后面排队,当队伍里只有一个人时,这个人的编号便是答案了。
在这里,我们用数组模拟队列。

点击查看代码
#include<stdio.h>
#define N 10010;

int main(){
	int hh = 1, tt;//hh(head)表示队头,tt(tail)表示队尾
	int q[N];
	int n, x;
	scanf("%d%d", &n, &x);
	tt = n;
	for(int i = 1;i <= n;i ++){
		q[i] = i;
	}
	while(tt > hh){//只要队尾在队头之后,就说明队伍里不止有一个人
		if(hh % x != 0){
			tt ++;
			q[tt % N] = q[hh % N];//让队头的人重新回到队尾,并对N取模防止数组空间不够
			hh ++;
		}
		else{
			hh ++;
		}
	}
	printf("%d", q[tt % N]);
	return 0;
}

这种写法码量极小,真是棒极了!

以上是个人提供的三种做法,如果各位有更好的做法,可以一起交流讨论。

posted @ 2022-12-18 14:04  20221312付安旭  阅读(61)  评论(0编辑  收藏  举报