您的位置:首页 > 其它

约瑟夫环O(N)和O(M*N)算法详解

2015-10-22 16:44 316 查看
问题描述:

已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列,求最后一个出列人的编号。

该问题可以用数组或者循环链表模拟,因为都是直接模拟,需要循环n次,每次数m次,所以时间复杂度都是O(n*m)。虽然复杂度比O(n)大,但是可以求出出圈的编号顺序。也正是因为该方法将每次出圈的人都找了出来,所以做了很多无用操作,复杂度才会高达O(n*m)。

下面是数组和链表方法的代码:

题意:n个人,从第k个人开始数1,数到m的人剔除,输出剔除的顺序。

#include <iostream>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MAXN 10000
int a[MAXN];
int N,k,m;
bool InCircle[MAXN];
typedef struct _node{
struct _node* prev;
struct _node* next;
int number;
}node;
node* MakeDLL(int n){
node *head = new node;/*Memory allocated to the head node*/
node *tail;
int i;
head->next = head;
head->prev = head;
head->number = 1;
tail = head;
for(i=2;i<=n;i++){
node *p = new node;/*Memory allocated to a new node*/
p->number = i;
p->next = tail->next;
p->prev = tail;
tail->next = p;
tail = p;
head->prev = tail;
}
return head;
}

void JosephDLL(node* head, int k, int m){
int i;
node *NodeToDelete,*q;
NodeToDelete = head;
for(i=1; i<k; i++)/*Get the element numbered k*/
NodeToDelete = NodeToDelete->next;
while(head->next != head){
for(i=1; i<m; i++){
NodeToDelete = NodeToDelete->next;/*Count m times*/
}
/*Delete operation,begin*/
q = NodeToDelete->next;
q->prev = NodeToDelete->prev;
NodeToDelete->prev->next = q;
printf("%d ",NodeToDelete->number);
if(NodeToDelete == head){/*If the member to be deleted is the head, redefine the head.*/
head = q;
}
free(NodeToDelete);
/*Delete operation,end*/
NodeToDelete = q;/*Count from the next node*/
}
printf("%d\n",head->number);
}
void JosephArr(){
int tmpN=N,i=k;
while(tmpN--){
int tmpM=m;
for(;tmpM;i++){/*Count m times*/
if(InCircle[i%(N+1)])/*If the member is in the circle, count on it(That is the operation tmpM--) */
tmpM--;
}
i--;
cout<<i%(N+1)<<" ";
InCircle[i%(N+1)]=0;/*delete the member i%(N+1) from the circle*/
}
}
int main(){
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
cout<<"Input N,k,m:"<<endl;
cin>>N>>k>>m;
for(int i=1;i<=N;i++){
a[i]=i;
InCircle[i]=1;
}
cout<<"Base on the array : ";
JosephArr();/*Base on the array*/
for(int i=1;i<=N;i++){
InCircle[i]=1;
}
node* head;
head = MakeDLL(N);
cout<<endl<<"Base on the doubly link list : ";
JosephDLL(head,k,m);/*Base on the doubly linked lists*/
return 0;
}


O(n)方法运用动态规划,或者说递推,或者说找规律,都行吧。虽然复杂度低,但是不能求出出圈的顺序。如果需要求出出圈的顺序,复杂度依然是O(n*m)。

该方法递推公式网上很多都能找到,但是却极少人给出递推过程。

我们发现,当从圈出来一个人之后,后一个人需要从1开始重新计数直到计到m。例如有n个人(1,2,3,… ,n-2,n-1),第m个人出来之后,需要从第m+1开计数。如果从m+1个人重新编号(即m+1个人编号为0,m+1为2,… …),那么对剩下的n-1个人的操作跟开始有n个人的时候操作就是一样的。

这就是可以运用动态规划的原因:n的情况跟n-1的情况存在着某种关系。而我们只需用动态方程将这种关系表达出来,问题就可以解决。

下面显示了如何进行重新编号:



设n个人围成一个圈的时候出列是第X’号,n-1个人围成一个圈的时候出列的是X号,并假设X已经求出来,那么根据上面的递推公式就可以反推求X’: 因为X=(X’*n+n-m)%n=X’*n%n+n%n-m%n=X’-m%n

所以X’=X+m%n=(X+m)%n

如果定义:dp
表示圈里有n个人的时候最后剩下那个人的编号,答案就是dp


那么就有dp
=(dp[n-1]+m)%n

但是dp[n-1]并不知道,所以dp
也没法求出。当然dp[n-1]也需要通过动态方程求出:

dp[n-1]=(dp[n-2]+m)%n。

同样dp[n-2],dp[n-3]….分别需要用dp[n-3],dp[n-4]求出。

最后dp[2]需要dp[1]求出,然而dp[1]=0是已知的。

所以从dp[2]开始依次可以求出dp[2],dp[3],dp[4]…dp


那么问题就解决了。

#include <iostream>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MAXN 10000
int dp[MAXN];
int joseph(int m, int n) {
dp[1] = 0;
for(int i = 2; i <= n; i++){
dp[i] = (dp[i-1] + m) % i;
}
return dp
;
}
int main(){
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
int m, n;
cin >> m >> n;
cout << joseph(m, n) << endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息