hello大家好,我是柱纸。昨天饭局上和小伙伴耍了几场丢手绢游戏,游戏时总有种十分熟悉的感觉,这不就是算法中类似约瑟夫环问题吗。那么今天就来实现一下吧
约瑟夫环是一个典型的问题
题意:已知有n个人围坐在一张圆桌周围,从其中第n个人开始报数m,下一个人依次报数,数到m的那个人出列;他的下一个人又从1开始依次向下报数,数到m的那个人又出列,依次重复下去,直到圆桌上剩余一个人。
实现肯定是要用一个头尾相接的循环链表实现了,当然也可以用更简单的思路:数组实现。不过在数组实现过程中移除元素的时间复杂度为O(n),而链表的时间复杂度为O(1)。
问题分析
分析问题后发现,问题本质其实就是循环链表的问题。看,n个人围成一个圈,没有开头和结尾,这就是循环链表;按照上一个的下一个顺序报数,就是链表的遍历;数到对应数字的那个人出列,就是链表的删除操作。假设我们给n=5个人每个人都分配一个编号,我们把他当成一个游戏,来模拟一下该链表
我们再来模拟一下游戏流程 例如我们要求从编号4的人开始报数,数到3的那个人数列(移除元素) 出列顺序依次为:
- 编号4的人开始数1,5数2,1数3,1出列。
- 2报数,4出列。
- 5报数,3出列。(此时只剩下两个数,接下来就是2和5之间的pk)
- 5报数,5出列。
- 2win。
代码实现
代码实现这一块儿,尽量把他讲明白哈,因为要分享给一个憨憨的朋友(狗头保命)
首先我们要创建一个链表,并把他的首尾连接起来,形成一个循环链表。
node*found()
{
//创建一个首元结点
node*head=(node*)malloc(sizeof(node));
head->elem=1;
head->next=NULL;
//temp指向首元结点head
node*temp=head;
for(int i=2;i<=5;i++)
{
node* p=(node*)malloc(sizeof(node)); //开辟一个结点
p->elem=i; //将i值赋给开辟的节点
p->next; //至此对新开辟节点p的初始化完成
//将新开辟的节点p和temp之间建立关系
temp->next=p;
temp=temp->;
}
temp->next=head; //将链表首尾相连起来,形成一个循环链表
return p;
}
之后进行出列操作
node* found(person* head)
{
int i;
int n;
int m;//这里我们假设n为4,m为3
node* tail = head;
person* p = head;
while (tail->next != head) //找到第一个节点的上一个结点,便于进行删除操作
{
tail = tail->next;
}
while (p->number != k) //找到第一个开始报数的人
{
tail = p;
p = p->next;
}
while (p->next != p) //当链表的后缀为他本身时,则表示该表中只有该数,结束
{
i = 1;
while (i < m) //找到报数报到m的人
{
tail = p;
p = p->next;
i++;
}
tail->next = p->next;
printf("出列的人的编号为%d\n", p->number);
free(p);
p = tail->next;
}
printf("出列的人的编号为%d\n", p->number);
free(p);
}
当然这种算法较为复杂,但胜在思路明确。除此之外还可以使用有序集合和递归,但链表实现是根本思想,希望这篇文章能对你有所帮助。