约瑟夫问题

约瑟夫问题

设编号分别为: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




约瑟夫问题


解题思路:

用一个不带头节点的循环链表来处理joseph问题:先构成一个有n个节点的单向循环链表,然后从第k结点从1开始计数,计数到m的时候,对应结点从链表中删除。然后再从被删结点的下一个节点继续从1开始计数.......,以此类推,直到所有的结点都列出时,程序结束。


joseph.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//一、单向循环链表
//特点:尾结点的指针域保存了头结点的地址。

/*二、数据类型*/
typedef int DataType;

typedef struct node
{
	DataType data;
	struct node *next;	
}loopnode,*looplist;

/*三、常用操作*/
/*1.创建空链表,只有一个头结点,且头结点的指针域保存了自己的地址*/
loopnode *create_empty_linklist()
{
	//1.为头结点分配空间
	loopnode *head = NULL;		
	head = (loopnode *)malloc(sizeof(loopnode));

	//把NULL写在前面是为了防止出现head = NULL.这种情况
	if(NULL == head)
	{
		printf("create head_loopnode is fail!\n");
		return NULL;
	}
	//2.将头结点的指针域赋head
	head->next = head;
	//3.返回头结点首地址
	
	return head;
}

//2.链表判空,比较head->next 和head。
int is_empty_linklist(loopnode *head)
{
	//1.若是head->next 为head,返回 1.
	return head->next == head;		
	//	否则返回0
}

/*3.头插法,在头节点后插入新节点*/
int insert_head_linklist(loopnode *head,DataType data)
{
	//1.为新节点在堆区分配空间,用指针temp来保存
	loopnode *temp = (loopnode *)malloc(sizeof(loopnode));	
	if(temp == NULL)
	{
		printf("create new_loopnode is failt\n");	
		return -1;
	}
	//2.把data数据填充到temp的数据域	
	temp->data = data;	
	//3.在头结点之后插入数据
	//<1>先让新节点的指针域保存头结点的指针域
	temp->next = head->next;	
	//<2>再让头节点的指针与保存新节点的首地址
	head->next = temp;	
	return 0;
}

//4.输出链表所有数据
int print_linklist(loopnode *head)
{
	//遍历所有节点,当指针域不为空的时候,
	//每遍历一个节点,就输出一个数据。
	loopnode *p = head->next;

	while(p != head)
	{
		printf("%d ",p->data);	
		p = p->next;
	}
	putchar('\n');

	return 0;
}

//5.创建不带头节点的带向循环链表。
//参数:带头结点的循环链表head

//思路:定义一个指针保存头结点的地址,
//然后遍历链表找到尾结点,然尾结点的指针域
//保存头节点下一个结点有效数据的地址,然后
//释放头结点.
loopnode *delete_head_linklist(loopnode *head)
{
	loopnode *p = head;		
	//遍历完成之后,p->next保存了头节点的地址.
	while(p->next != head)
	{
		p = p->next;
	}

	//尾结点保存头节点下一个节点的地址
	//此时p->next为新的第一个结点的地址
	p->next = head->next;

	free(head);
	head = NULL;

	return p->next; 
}

//6.输出新的删除了头结点的数据
int print_delete_head_data(loopnode *head)
{
	loopnode *temp = head;

	//输出第一个数据
	printf("%d ",head->data);

	//从第二个数据开始遍历
	while(temp->next != head)
	{
		printf("%d ",temp->next->data);	
		temp = temp->next;
	}
	putchar('\n');

	return 0;
}

void joseph_true(int n,int k,int m)
{
	int i = 0;	
	loopnode *head = create_empty_linklist();
	loopnode *temp = NULL;

	//因为是链表,所以我们需要倒着插入数据
	for(i = n;i > 0;i--)
	{
		insert_head_linklist(head,i);	
	}
	print_linklist(head);

	head = delete_head_linklist(head);
	print_delete_head_data(head);

	loopnode *p = head;
	
	//找到第k个人,p需要移动k - 1次
	//此时p指向了第k个人
	//k = 3
	for(i = 0;i < k - 1;i++)	
	{
		p = p->next;	
	}

	//循环m次,数到m的人出列,直到循环链表中的只剩
	//一个节点(p->next == p)结束,
	//循环内,数m次,指针p需要移动m - 1次,然后把移动后p的后一个结点
	//的值给当前结点,然后把链表重新连接,在删除后一个结点
	//上述删除之后,我们的p已经保存了下一个节点的地址了,不需要移动了
	while(p != p->next)
	{
		//找到要删除的m的结点
		for(i = 0;i < m - 1;i++)	
		{
			p = p->next;	
		}
		printf("%-4d",p->data);
		//把m后一个节点的值赋值给m
		p->data = p->next->data;

		//temp保存后一个结点的地址
		temp = p->next;
		//把链表重新链接起来
		p->next = p->next->next;	

		free(temp);
		temp = NULL;
	}
	//循环结束,我们为空链表。只剩最后一个结点head,head中有数据。
	printf("%-4d\n",p->data);
	free(p);
	p= NULL;
	return ;
}

//注:此程序有bug,若是m = 1的话,我们的
//
		//找到m前一个结点的地址
//		for(i = 0;i < m - 2;i++)	
//		{
//			p = p->next;	
//		}
void joseph(int n,int k,int m)
{
	int i = 0;	
	loopnode *head = create_empty_linklist();
	loopnode *temp = NULL;

	//因为是链表,所以我们需要倒着插入数据
	for(i = n;i > 0;i--)
	{
		insert_head_linklist(head,i);	
	}
	print_linklist(head);

	head = delete_head_linklist(head);
	print_delete_head_data(head);

	loopnode *p = head;
	
	//找到第k个人,p需要移动k - 1次
	//此时p指向了第k个人
	//k = 3
	for(i = 0;i < k - 1;i++)	
	{
		p = p->next;	
	}

	//循环m次,数到m的人出列,直到循环链表中的只剩
	//一个节点(p->next == p)结束,
	//循环内,数m次,指针p需要移动m - 1次,删除第m个人需要找到
	//前一个人,指针p移动m - 2次,
	//下一次从下一个人开始,p再移动一次
	while(p != p->next)
	{
		//找到m前一个结点的地址
		for(i = 0;i < m - 2;i++)	
		{
			p = p->next;	
		}
		temp = p->next;
		printf("%-4d",temp->data);
		p->next = p->next->next;

		free(temp);
		temp = NULL;
		//下一次,从下一个人开始,p在移动一次
		p = p->next;
	}

	//循环结束,我们为空链表。只剩最后一个结点head,head中有数据。
	printf("%-4d\n",p->data);
	free(p);
	p= NULL;
	return ;
}

int main(int argc, const char *argv[])
{
	int n = 8,k = 3,m = 4;
	
	joseph(n,k,m);
	printf("==========================\n");
	joseph_true(n,k,m);
	return 0;
}


运行结果:

约瑟夫问题