一个简单的约瑟夫问题,却有许多种写法。

n 个人围成一个圆圈,第1个人从1开始顺时针报数,  报到m的人,令其出列。然后再从下一个人开始,从1顺时针报数,报到第m个人,再令其出列,…,如此下去,  直到圆圈中只剩一个人为止。此人即为优胜者。

为了求出胜利者,我们需要排除掉报数后出去的人,有人用数组来模拟人们报数的过程,除掉n-1个人之后,便得出了胜利者。

也可以选择使用循环链表,每次循环到第m个人,就把他从链表中删去。 这里结束循环的条件可以是两种,一个是循环链表的结点元素p->next == p, 也就是只剩下一个人时结束,但我写起来却有点尴尬,因为当m为1时,我没办法找到要删的第一个结点的前驱结点,所以还是用除掉n-1个人做判断条件吧。

 

View Code
View Code 
 1 #include<iostream>
 2 using namespace std;
 3 #include<stdlib.h>
 4 typedef struct l
 5 {
 6     int n;
 7     struct l * next;
 8 }L;
 9 void InitList( L* &head, int n)                        //建立没有头结点的链表 注意&
10 {
11     L * tail;
12     L * temp;
13     head = NULL;
14     tail = NULL;
15     int i;
16     for( i = 0; i < n; i++)
17     {
18         temp = (L *) malloc( sizeof( L ));
19         temp->n = i+1;
20         if( head == NULL )
21         {
22             head = temp;
23         }
24         if( tail != NULL)
25             tail->next = temp;
26         tail= temp;
27         if( tail->next != NULL)
28             tail->next = NULL;
29     }
30     tail->next = head;
31 }
32 void List( L * head, int m, int n)
33 {
34     int i = 1;                                        //因为是从第一个结点开始 所以i初始为1
35     L * p;
36     p = head;    
37     n--;
38     while(n--  )                                    //在只剩下一个结点前
39     {
40         while( i!=m)                                //每次前进m-1个结点 到达从起始位置起第m个结点
41         {
42             
43             p = head;
44             head = head->next;
45             i++;    
46         }
47         i = 1;                                     
48         p->next = head->next;                        //该元素删掉 i从下个元素开始
49         head = p->next;    
50         //free(l);
51 
52     }
53     //cout << r << endl;
54     cout << head->n << endl;
55     
56 
57 }
58 
59 void List2( L * head)                             
60 {
61     while( head)
62     {
63         cout << head->n;
64         head = head->next;
65     }
66     cout << endl;
67 }
68 
69 
70 int main()
71 {
72     L * p;
73     p = (L*)malloc(sizeof(L));
74     int n, m;
75     while(1)
76     {
77         cin >> n >> m;
78         if( n == 0 && m==0)
79             break;
80         InitList( p, n);                            //建立一个n个节点的链表 
81         List( p , m, n);                            //找出胜利者 
82         //List2(p);
83         free(p);
84     }
85         return 0;
86 }

 

 

 

 

更为方便的一种写法是用一个带头结点的单链表,每次数一个数就拆下第一个数据接到尾部,数到m时拆下的那个结点不再接到尾部,然后继续重新开始数,丢掉n-1个人之后,胜利者求出。这种写法虽然略微多了点操作,却比较简单好写。

但是 当人们用数学方法来求约瑟夫问题时,上面的全部方法都被超越了,当我们求出第一个被排除的人之后,剩下的n-1个人继续开始报数,这是一个新的约瑟夫环,而这个约瑟夫环的胜利者一定也是原来那个约瑟夫环的胜利者,由于这个约瑟夫环的第一个人是上个环的第k+1号,k = m % n - 1(编号从0开始,因为编号从1开始要考虑(x+k)%n = 0的情况)。所以新环的胜利者编号x与原环胜利者编号y的关系为 y = ( x + k)%n.

同样的n-1个人组成的环的胜利者编号可以由n-2个人组成的胜利者编号求出,n-2由n-3求出。。。

f[i]表示i个人玩游戏的胜利者编号  最后的结果自然是f[n]递推公式      f[1]=0;           f[i]=(f[i-1]+m)%i;  (i>1)

程序时间复杂度跟代码长度都得到了降低

#include<iostream>
using namespace std;
int main()
{
 int n,m, r ;
 cin >> n >> m;
 r = 0;
 for(int i = 2 ; i <= n; i++)   //递归n-1次
  r = (r+m)%i;
 cout << r<< endl;     //如果编号为从1开始 则输出r+1
 return 0;
}


 //编号从1开始也可以这么写

r = 1;

 for( int i = 2 ; i <= n; i++)
 {
         r = (r+m)%i;
         if( r == 0 )  //因为编号从1开始时 如果胜利者在原来环的位置是最后一位,这里求出的结果是为0的
         r = i;
  }

 

 

 

但是有另外一种情况,在n极大,而m较小时,可以用另一种方法

 


如果做一个表,我们从1开始报数  每次报到m的倍数时出局,最终报数为 m * n的人为胜利者

1

2

3

4

5

6

7

8

9

10

11

12

 

13

14

 

15

16

 

17

18

   

19

20

   

21

 

22

     

23

24

       

25

     

26

         

27

     

28

           
     

29

           
     

30

           

 

由上表可知,第四人为胜利者,设胜利者在i行的报数为 X i , 只有X i不是 m 的倍数时才有胜利的可能,所以可以设 Xi = k * m + j  ,     k 为对  Xi / m 的取整。(  j < m , j≠0)

 

可知,当前出局的人数为 k , 剩下的人数为 n – k ,  胜利者在 i 行的报数为 Xi ,那么他在
 i + 1 行的报数一定为 Xi 加上剩余人数 ,即 Xi+1 = Xi + n – k 

 

 可推出 Xi+1 = k * m + j + n – k = k*(m -1 ) + j + n

 

 反过来 若已知胜利者在第 i+ 1 行的报数  Xi+1 ,可以通过Xi+1 – n =k * (m – 1) + j 推出
 j = (Xi+1 – n ) % ( m – 1), 如果j=0, 则j = m-1, 
 可以通过 j 来求出 k = ( Xi+1 – n – j )/ ( m – 1)

 

 有了j 和 k 便可以通过Xi+1 求Xi,  由最后一行一定是 m * n 反向推导,推导至第一行

#include<iostream>
using namespace std;
int main()
{
	int n, m;
	int j, k;
	cin >> n >> m;	
	
	int Xn = n * m;		//最后一行一定为 n * m	
	while( Xn > n)
	{
		j = ( Xn - n) % ( m -1 );
		if( j == 0)
			j = m -1;
		k = ( Xn - n - j)/ (m -1);
		Xn = k * m + j;
	}
	cout << Xn << endl;
	return 0;
}

 

  由于一次可以消去多个数据,时间复杂度取决于 m与n的关系,m与n相差越大,此算法越优于上一种算法

 

posted on 2012-07-31 20:31  BUG易  阅读(256)  评论(0编辑  收藏  举报