约瑟夫环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的人剔除,输出剔除的顺序。
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的情况存在着某种关系。而我们只需用动态方程将这种关系表达出来,问题就可以解决。
下面显示了如何进行重新编号:
![](https://img-blog.csdn.net/20151022163937471)
设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
。
那么问题就解决了。
已知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; }
相关文章推荐
- [C/C++]反转链表
- 动易2006序列号破解算法公布
- ruby 数组使用教程
- Ruby中的数组和散列表的使用详解
- C#实现AddRange为数组添加多个元素的方法
- C#实现基于链表的内存记事本实例
- C#动态调整数组大小的方法
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 详解Lua中的数组概念知识
- Perl中的列表和数组学习笔记
- 探索PowerShell (八) 数组、哈希表(附:复制粘贴技巧)
- C#中数组初始化与数组元素复制的方法
- C#交错数组用法实例
- 超大数据量存储常用数据库分表分库算法总结
- Linux Shell 数组建立及使用技巧
- PowerShell数组的一些操作技巧
- C#数据结构与算法揭秘二
- C#通过yield实现数组全排列的方法
- C#不重复输出一个数组中所有元素的方法