约瑟夫环

210 阅读3分钟

hello大家好,我是柱纸。昨天饭局上和小伙伴耍了几场丢手绢游戏,游戏时总有种十分熟悉的感觉,这不就是算法中类似约瑟夫环问题吗。那么今天就来实现一下吧

约瑟夫环是一个典型的问题

题意:已知有n个人围坐在一张圆桌周围,从其中第n个人开始报数m,下一个人依次报数,数到m的那个人出列;他的下一个人又从1开始依次向下报数,数到m的那个人又出列,依次重复下去,直到圆桌上剩余一个人。

实现肯定是要用一个头尾相接的循环链表实现了,当然也可以用更简单的思路:数组实现。不过在数组实现过程中移除元素的时间复杂度为O(n),而链表的时间复杂度为O(1)。

问题分析

分析问题后发现,问题本质其实就是循环链表的问题。看,n个人围成一个圈,没有开头和结尾,这就是循环链表;按照上一个的下一个顺序报数,就是链表的遍历;数到对应数字的那个人出列,就是链表的删除操作。假设我们给n=5个人每个人都分配一个编号,我们把他当成一个游戏,来模拟一下该链表

屏幕截图 2022-02-16 123040.jpg

我们再来模拟一下游戏流程 例如我们要求从编号4的人开始报数,数到3的那个人数列(移除元素) 出列顺序依次为:

  1. 编号4的人开始数1,5数2,1数3,1出列。
  2. 2报数,4出列。
  3. 5报数,3出列。(此时只剩下两个数,接下来就是2和5之间的pk)
  4. 5报数,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);
}

当然这种算法较为复杂,但胜在思路明确。除此之外还可以使用有序集合和递归,但链表实现是根本思想,希望这篇文章能对你有所帮助。