• 约瑟夫(环)问题(Josephus problem)


    问题描述:皇帝决定找出全国中最幸运的一个人,于是从全国选拔出 n 个很幸运的人,让这 n 个人围着圆桌进餐,可是怎么选择出其中最幸运的一个人呢?皇帝决定:从其中一个人从 1 开始报数,按顺序数到第 k 个数的人自动出局,然后下一个人从 1 开始报数,数到 k 的人出局……。如此直到最后只剩下约瑟夫一人,然后他就成为全国最幸运的人。请问约瑟夫最初的位置?(注:原问题略显暴力,故自创此趣味题目)

    分析:把第一个开始报 1 的人标定为 1,然后按报数顺序依次标定其余的人为:2,3,……,n - 1,n。按规则进行淘汰,直到最后剩一个数字,这个数字就是约瑟夫的位置。

    解决方案:

    1. 模拟法(simulation)

    数组模拟,时间复杂度最高为 O(n3) (当k≈n时 ) ,空间复杂度O(n)

     1 /********** 用数组模拟 *************/
     2 void findNext(bool *out, int n, int &curPosition){
     3     if(!out[curPosition]) {
     4         int pNext = (curPosition + 1) % n;
     5         if(!out[pNext])
     6             curPosition = pNext;
     7         else{
     8             curPosition = pNext;
     9             while(out[curPosition])
    10                 curPosition = (curPosition + 1) % n;
    11         }
    12     }else
    13     {
    14         while(out[curPosition])
    15             curPosition = (curPosition + 1) % n;
    16     }
    17 }
    18 int josephus(int n, int k)
    19 {
    20     if(n < 1 || k < 0) return -1;
    21     if(n == 1) return n;
    22     bool *out = new bool[n];   /********* 记录是否出局 *********/
    23     for(int i = 0; i < n; ++i)
    24         out[i] = false;
    25     int current = 0;
    26     int n2 = n;
    27     while(n2 != 1)
    28     {
    29         int cnt = k;
    30         while(--cnt)
    31             findNext(out, n, current);
    32         out[current] = true;
    33         findNext(out, n, current);
    34         --n2;
    35     }
    36     delete[] out;
    37     out = NULL;
    38     return (current + 1);
    39 }
    Code

    循环链表模拟:时间复杂度O(n),空间复杂度O(n)

    /********** 循环链表 *************/
    struct ListNode{
    	int val;
    	ListNode * next;
    	ListNode(int x):val(x), next(NULL) {}
    };
    
    int josephus(int n, int k)
    {
    	if(n < 1 || k < 1)
    		return -1;
    	if(n == 1) return n;
    	ListNode *head = new ListNode(1);
    	ListNode *prior = head; 
    	for(int i = 2; i <= n; ++i)
    	{
    		ListNode *tem= new ListNode(i);
    		prior->next = tem;
    		prior = prior->next;
    	}
    	prior->next = head;
    	while(head->next != head)
    	{
    		int cnt = k;
    		while(--cnt)
    		{
    			head = head->next;
    			prior = prior->next;
    		}
    		prior->next = prior->next->next;
    		ListNode *current = head; 
    		head = head->next;
    		delete current;             /*** 只释放堆内存空间,局部指针自动回收 ***/
    	}
    	return head->val;
    }
    

     2.建模法(modeling)

    使用队列建模。

     1 /********** 用队列(注:使用STL可简单化) *************/
     2 bool ERROR = false;
     3 typedef int ELEM;
     4 struct Node{
     5     ELEM val;
     6     Node *next;
     7     Node(ELEM e):val(e), next(NULL){}
     8 };
     9 struct queue{
    10     queue():front(NULL), tail(NULL) {}
    11     ELEM pop();
    12     void push(ELEM val);
    13     bool empty();
    14 private:
    15     Node *front;
    16     Node *tail;
    17 
    18 };
    19 ELEM queue::pop(){
    20     if(front == NULL){
    21         ERROR = true;
    22         return -1;
    23     }else{
    24         ELEM v = front->val;
    25         front = front->next;
    26         return v;
    27     }
    28 }
    29 void queue::push(ELEM val){
    30     Node *p = new Node(val); 
    31     if(front == NULL)
    32         front = tail = p;
    33     else
    34     {
    35         tail->next = p;
    36         tail = tail->next;
    37     }
    38 }
    39 bool queue::empty(){
    40     if(front == NULL)
    41         return true;
    42     else 
    43         return false;
    44 }
    45 
    46 int josephus(int n, int k)
    47 {
    48     if(n < 1 || k < 1) return -1;
    49     if(n == 1) return 1;
    50     queue qu;
    51     for(int i = 1; i <= n; ++i)
    52         qu.push(i);
    53     int result = 0;
    54     while(!qu.empty())
    55     {
    56         for(int i = 1; i <= k-1; ++i)
    57             qu.push(qu.pop());
    58         result = qu.pop();
    59     }
    60     return result;
    61 }
    Code

     

    3. 数学推理 && 动态规划

    初始:0 1 ... (k-2) (k-1)  k ... (N-1)

    K 出局:                    新的顺序:                         

    k                              0                                                                      

    ...            p               ...                                                           

    N-1         映              N - k - 1                                                P(x) = (x - k + N) mod N                             

    0             射              N - k                                              令:y = P(x) = (x - k + N) mod N                             

    1                              N - k + 1                                        则,x = (y + k - N)             mod N  

    ...                             ...                                                           = (y + k)                   mod N

    k-2                           N - 2                                           P-1(x) = (x + k) mod N 

    设 f(N,k) 为最后所得的数字,则:

    f(N,k) = P-1( f(N-1,k) ) = (f(N-1,k) + k) mod N

     所以有如下递推公式:

                                        

    int josephus(int n, int k)
    {
    	if(n < 1 || k < 1) return -1;
    	if(n == 1) return 1;
    	int result = 0;
    	for(int i = 2; i <= n; ++i)
    		result = (result + k) % i; 
    	return result+1;
    }
    

     另外,简洁的递归:(不推荐,递归栈太小,容易溢出) 

    int josephus(int n, int k)
    {
    	if(n < 1 || k < 1) return -1;
    	if(n == 1) return 1;
    	else
    		return ((josephus(n - 1, k) + k - 1) % n + 1);
    }
    

    最后,当 k = 2 时,如下公式可直接求出:

       

     代码为:

    int n = 1000;
    cout<< 2*(n - pow(2.0, int(log((float)n) / log((float)2))))+1 <<endl;
    
  • 相关阅读:
    work two year[转]
    知名技术博客内容聚合网站
    VS2010注册码
    某公司的一个题面试题(wfcfan)
    asp.net控件开发基础系列
    .NET (C#) Internals: Delegates1
    可空类型细微见真知!
    C#中操作XML Node节点细节操作
    sql server数据库性能的优化
    字符串精确匹配算法改进的探讨
  • 原文地址:https://www.cnblogs.com/liyangguang1988/p/3620007.html
Copyright © 2020-2023  润新知