题目:
(约瑟夫环)
设编号为1,2,…n的n个人围坐一圈,约定标号为k(1<=k<=n)的人从1考试报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列(或者求最后剩余的一个人)。
常用的有三种解法1-数组、2-循环列表、3-递归。
第一种解法:用数组来对约瑟夫环求解。
思路:定义一个数组,给其开辟空间,然后将编号放进数组中,即对数组初始化,遇到第k个则把第k个删除,将之后的元素向前移位。具体程序如下:
void Ysf_Cirle(int *space, int m, int k)
{
for(int i=0; i<m; ++i) //初始化
space[i] = i+1;
int pos = 0;
while(m > 1)
{
for(int j=0; j<k-1; ++j)
pos = (pos+1) % m; //在此处求模,这样就可以达到循环的目的。
cout<<space[pos]<<" "; //若只求最后一个人,则屏蔽掉该句
for(int n=pos; n<m-1; ++n) //元素向前移位处理
space[n] = space[n+1];
m--;
}
cout<<space[pos]<<endl;
}
int main()
{
int *space;
int m, k;
cout<<"input 人数:>";
cin>>m;
cout<<"input 间隔:>";
cin>>k;
space = (int*)malloc(sizeof(int)*m);
Ysf_Cirle(space, m, k);
free(space);
return 0;
}
第二种解法:用循环链表来解。
思路:用现成的STL里面的链表,建立循环链表的环,随后将编号放进该环中,随后用迭代器定义出pos是起始位置。随后进行循环,遇到第k个节点删除,直至链表中剩余一个节点(注:在这块的难点在于,循环链表和迭代器定义的头节点中是没有编号的,因为我们每次循环的时候要跳过pos头结点)。具体程序如下:(#include)
void Ysf_Cirle(int m, int k)
{
//建立循环链表的环
list<int> mylist;
for(int i=1; i<=m; ++i)
{
mylist.push_back(i);
}
//
list<int>::iterator pos = mylist.begin(); //pos是起始位置
while(mylist.size() != 1)
{
for(int j=0; j<k-1; ++j)
{
pos++;
if(pos == mylist.end()) //跳过头结点
pos++;
}
cout<<*pos<<" "; //若只求最后一个人,则屏蔽掉该句
pos = mylist.erase(pos);
if(pos == mylist.end()) //跳过头结点
pos++;
}
cout<<*pos<<endl;
}
int main()
{
int m, k;
cout<<"input 人数:>";
cin>>m;
cout<<"input 间隔:>";
cin>>k;
Ysf_Cirle(m, k);
return 0;
}
第三种解法:递归
现在假设m=10
0 1 2 3 4 5 6 7 8 9 k=3
第一个人出列后的序列为:
0 1 3 4 5 6 7 8 9
即:3 4 5 6 7 8 9 0 1(*)
我们把该式转化为:
0 1 2 3 4 5 6 7 8 (**)
则你会发现: ((**)+3)%10则转化为(*)式了
也就是说,我们求出9个人中第9次出环的编号,最后进行上面的转换就能得到10个人第10次出环的编号了
设f(m,k,i)为m个人的环,报数为k,第i个人出环的编号,则f(10,3,10)是我们要的结果
当i=1时, f(m,k,i) = (m+k-1)%m
当i!=1时, f(m,k,i)= ( f(m-1,k,i-1)+k )%m
所以程序如下:
int fun(int m,int k,int i)
{
if(i==1)
return (m+k-1)%m;
else
return (fun(m-1,k,i-1)+k)%m;
}
int main(int argc, char* argv[])
{
for(int i=1;i<=10;i++)
printf("第%2d次出环:%2d\n",i,fun(10,3,i));
return 0;
}