约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。
猴子选大王只是约瑟夫问题衍生出的一个典型算法例题,本篇博文介绍两种解法:暴力法+回溯法。
暴力法:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=30;
int main()
{
int a[N];
memset(a,0,sizeof(a));
cout<<"此处有"<<N<<"只猴子!"<<endl;
for(int i=0;i<N;++i)
{
a[i]=i+1;
}
for(int i=0;i<N;++i)
{
if(i%5==0)
{
cout<<endl;
}
printf("%3d ",a[i]);
}
int getout;
cout<<endl<<"请输入出队的序号:";
cin>>getout;
int current=0;
for(int i=N;i>0;--i)
{
current=(current+getout-1)%i;//这里是关键,每趟出队的序号
printf("第%3d只猴子滚出去.\n",a[current]);
for(int j=current;j<i-1;++j)//出队过后便前移元素
{
a[j]=a[j+1];
}
}
cout<<"猴王是第"<<a[current]<<"只猴子!"<<endl;
return 0;
}
大家可以看看结果:
回溯法:
都说回溯大法好,今天我们来瞅瞅!
#include<stdio.h>
#include<iostream>
using namespace std;
const int N=9;
int main()
{
int a[N];
for(int i=0;i<N;++i)
{
a[i]=i+1;
}
for(int i=0;i<N;++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
int index;
cout<<"输入每次要出队的序号呗,大爷:";
cin>>index;
for(int i=N-1;i>=0;--i)//小伙伴们,能看出来,算法的精髓吗?其实i大于0就行了,不过...懒得改了
{
for(int k=1;k<=index;++k)//每次出队时,把要出队的序号排在数组末尾
{
int temp=a[0];
for(int j=0;j<i;++j)
{
a[j]=a[j+1];
}
a[i]=temp;
for(int p=0;p<N;++p)//测试每轮数组位置的变动
{
cout<<a[p]<<" ";
}
cout<<endl;
}
}
for(int i=N-1;i>=0;--i)
{
cout<<"第"<<a[i]<<"只猴子滚出去!"<<endl;
}
cout<<"猴王是"<<a[0]<<"只猴子!"<<endl;
return 0;
}
这题可算是熟悉约瑟夫环的一个比较好的例子了,对回溯也是很好的诠释。
再回顾一下百度百科对回溯的诠释:
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
用回溯算法解决问题的一般步骤为:
1、定义一个解空间,它包含问题的解。
2、利用适于搜索的方法组织解空间。
3、利用深度优先法搜索解空间。
4、利用限界函数避免移动到不可能产生解的子空间。
问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。