1. 程式人生 > >使用環形連結串列實現約瑟夫環

使用環形連結串列實現約瑟夫環

連結串列實現Josephus約瑟夫環問題如下,輸出每輪殺掉的人的編號,並且輸出最後剩下的一名幸運者。

標頭檔案如下

#ifndef JOSEPHLIST_H
#define JOSEPHLIST_H
#include <stdio.h>
typedef struct node *link;
struct node{
    int item;
    link next;
};
link make_node(int item);
void free_node(link p);
void make_joseph_circle(int m);
int joseph_number(int m, int n);
void insert(link p);
void delete_node(link p);
void traverse(void (*visit)(link));
void destroy(void);
#endif

實現如下

#include <stdlib.h>
#include "josephlist.h"
static link head = NULL;
static link tail = NULL;

link make_node(int item)
{
    link p = new node;
    p->item=item;
    p->next=NULL;
    return p;
}

void free_node(link p)
{
   if(p!=NULL)
      free(p);
}

void insert(link p)
{
    if(head == NULL && tail ==NULL){  
        head = p;
        tail = p;
        p->next=head;

    }else{
        tail->next = p;
        tail = p;
        tail->next = head;
    }

}


void delete_node(link p)
{
    link pre;
    if(head==tail&&head==p) {
        head = NULL;
        tail = NULL;
        return;
    }else if(head==tail&&head!=p) {
        printf("impossible\n");
        return;
    }else if(p==head){
        head = head->next;
        tail ->next = head; // IMPORTANT O(∩_∩)O
        return;    
    }   

    for(pre=head;pre;pre=pre->next) {
        if(pre->next == p) {
              pre->next = p->next;
              if(p==tail)  
                  tail=pre;            
              return;
        }
    }
}

void traverse(void (*visit)(link))
{
    link p;

    for(p=head;p!=tail;p=p->next)
        visit(p);
    visit(tail);
}

void destroy(void)
{
    link p= head;
    link q= head; 
    while(p!=tail) {
        q = p;
        p = p->next;   
        free_node(q);
    }
    if(tail!=NULL)
        free_node(tail);
    head = NULL;
    tail = NULL;
    return;

}

void make_joseph_circle(int m)
{
    if(head!=NULL)
        destroy();
    for(int i=1;i<=m;i++){
        link p =  make_node(i);
        insert(p);
    }
    return;
}
int joseph_number(int m, int n)
{
    make_joseph_circle(m);
    link p=head;
    link temp_next=p;
    int count=1;
    while(count<m) {
        for(int i=0;i<n-1;i++) {
            p = p->next;
        }
        printf("%d\t%d\n",count,p->item);
        temp_next = p->next;
        delete_node(p);
        free_node(p);
        p = temp_next;
        count++;
    }
    printf("LUCKY Joseph:%d\n\n",p->item);

    int result = p->item;
    if(p!=NULL)
        free_node(p);
    head=NULL;
    tail=NULL;
    return result;
 
}


main函式如下

#include <stdio.h>
#include "josephlist.h" 

void print_item(link p)
{
    printf("%d\n",p->item);
}

int main()
{
    printf("joseph(6,5)\n");
    joseph_number(6,5);
    printf("joseph(6,3)\n");
    joseph_number(6,3);
    printf("joseph(6,4)\n");
    joseph_number(6,4);
    
    return 0;
}

執行結果如下:


現在來探索一下每次得到的約瑟夫數的規律,從最簡單的開始。假設每次報數的時候,都1,2,1,2這樣的報數,報數為2的在此輪出列。那麼,總人數變化時,約瑟夫數如何變化呢?為了簡單,現在假設人數從1增長到25,每次報數為2時的變化情況:

把main函式改為如下:

#include <stdio.h>
#include "josephlist.h" 

void print_item(link p)
{
    printf("%d\n",p->item);
}

int main()
{
    int total=25;
    int result_people[26];
    for (int i=1;i<=total;i++) {
        result_people[i]=joseph_number(i,2);
    }
    for(int i=1;i<=total;i++) 
    {
        printf("%d\t",i);
    }
        printf("\n");
    for(int i=1;i<=total;i++) 
    {
        printf("%d\t",result_people[i]);
    }
        printf("\n");
   
    return 0;
}
最終結果如下,可以看到,結果是有規律的:



約瑟夫數的結果構成了一個這樣的數列

1,
1,3
1,3,5,7
1,3,5,7,9,11,13,15,
...

那麼,當每次報數為2時,如何從數學上推導約瑟夫數隨著總人數變化的遞推公式呢?又如何把報數從2推廣到N呢?還沒想出來。。。