Josephus问题:N个人排成一个圈,从其中个人从1开始报数,数到m的人自动出列,接着下一个人又从1开始报数。如此循环往复,直到只剩下一个人,则此人获胜。
假设出列的人是被毙了,这时你有两个方式:
(1)标记法:记录完所有loser,最后判断出winner时,再把loser一起毙掉
(2)直接删除法:数到一个loser毙一个,直到出现winner
上面的方法(1)便于在顺序线性表(数组)中使用,(2)便于在动态链表中使用 。
下面就两种方法给出代码:
(1)标记法
它的空间复杂度是 O(n),时间复杂度是
#include <iostream>
using namespace std;
int main(){
int num,interval;
int *a;
int i,k,j;
cout<<"How many people?";
cin>>num;
cout<<"interval:"; //报数间隔
cin>>interval;
a=new int [num];
for(i=0;i<num;i++){ //给每个人编号
a[i]=i+1;
}
i=-1;
k=0;
while(1){
for(j=0;j<interval;){
i=(i+1)%num; //很重要,取模法,使得循环计数
if(a[i]!=0)j++;
}
if((k+1)==num) break;
cout<<a[i]<<" "; //输出loser
a[i]=0;
k++;
}
cout<<"\nThe winner is:";
for(int i=0;i<num;i++){
if(a[i]!=0) cout<<a[i];
}
return 0;
}
(2)直接删除法:
空间复杂度:O(n),但是我找到loser就delete,使得堆的利用效率更高。
时间复杂度:O(3n),也就是O(n)
#include <iostream>
using namespace std;
typedef struct P{
int num;
struct P *next;
}Node, * Link;
int main()
{
void CreateList(Link &L,int n);
Node *Start(Link L,int start);
// 定义循环链表结点
Link jose;
Node *cur,*curr; //cur是当前位置,curr跟踪cur
int n, start, interval;
int i,no=0; //no计数器
//输入数据
cout << "Input the number of the people: ";
cin >> n;
cout << "Input the interval: ";
cin >> interval;
cout << "Input the start position: ";
cin >> start;
//建立循环单链表,并初始化数据
CreateList(jose,n);
//设置起始位置
cur=curr=Start(jose,start);
cout << "\nout order:";
while(1){
i=1;
while(i<interval){
curr=cur;
cur=cur->next;
i++;
}
curr->next=cur->next;
cout <<cur->num<<" ";
delete cur; //当然可以写一个Delete函数,专门删除节点,那么我就可以不设跟踪指针了
no++; //不过有delete函数的话,因为要遍历链表,那么时间复杂度更高
if((no+1)==n) break;
cur=curr=curr->next;
}
// 输出优胜者
cout<<"\nThe winner is:"<<curr->num; //注意,这里不用jose->num,因为,jose已经丢了,这就是不带头结点的不好之处。
return 0;
}
void CreateList(Link &L,int n){ //建立单向循环链表(不带头结点)
Node *p; //如果带上头节点的话,循环去Kill节点的时候势必受到头结点的影响,不好控制
L=new Node;
if(!L)exit (OVERFLOW);
L->next=L;
L->num=1;
for(int i=n;i>1;i--){
p=new Node;
if(!p)exit (OVERFLOW);
p->num=i;
p->next=L->next;
L->next=p;
}
}
Node *Start(Link L,int start){
Node *p=L;
while(p->num<start){
p=p->next;
}
return p;
}