约瑟夫环问题

前言

欢迎大家光临我的博客,本篇文章将着重介绍约瑟夫环问题的分析,算法设计以及c风格代码的基本实现。

本篇文章的约瑟夫环问题的描述

设有编号为1,2,...n的n(n>0)个人围坐成一个圈,每个人持有一个密码m,从第一个人开始报数,报到m停止,报m的人出圈,如此下去,直到所有人全部出圈为止。当任意给定n和m后,设计算法求n个人出圈的次序。

问题分析

一共有N个人,所有人围成一个闭环。开始时,选择第一个人,根据他的密码值m,从他开始(包括他),从1开始向后报数,报到m为止,此时所指向的人出圈。选择刚才出圈的人的下一个人,根据他的密码值m,从他开始(包括他),从1开始向后报数,......如此循环下去,直到全部人出圈。

不妨设p(n)表示编码为n的人,p(n).m表示这人的密码,p(n).next表示他的下一个人。

 

假设编码n的人向后数k后,指向编码z人,则p(n+k-1) 不一定等于 p(z),也就是 n+k-1不一定等于z。原因是,当有人出圈后,构成这个圆环的编码数将不再连续(从正整数的角度来看),也就推不出z = n+k-1的线性关系。其实,在开始前,圆环也是不连续,因为从N到1是跳跃的。

为了解决这个非线性问题,我们可以通过链表将它划分为若干个的线性问题。

如何理解链表的线性?

        我们知道链表是由若干个两两关联的节点构成的,任何一个节点都清楚地知道它的上一个节点和下一个节点(这里说的是双向链表)。笔者认为这样的情景中蕴涵了一种直达关系,也就是线性。所以链表蕴涵了若干个线性关系。

所以我们可以使用链表模型来解决这个问题。

算法设计

根据问题分析的结果,我们可以写出以下伪代码:

0     定义 v = (b,m)  其中 v是b与m的组合,b表示编码,m表示密码 
1     定义 S = {v1,v2,v3...end} 其中 S为链表,end是空元素,用来表示元素的终结
2     定义 R 为出圈编码的有序链表
3     令 p = S中的第一个元素
4     if S的元素个数!=0 :
5         令 n = 1
6         令 k = p的密码
7         if n < k :
8             令 n = n + 1
9             令 p = p的下一个元素
10            if p是S的end :
11                令 p = S的第一个元素  
12            repeat
13        令 p的编码加入到R
14        令 tmp = p的下一个元素
15        将 p从S中移除
16        令 p = tmp
17        repeat

首先从S中选择第一个元素,在S的元素个数不等于0的前提下,不断将元素从S中移除。

第5行~第12行,实现了从1开始报数。

第9行与第10行,n ,p是同步变化,实现了数数的细节。

第11行,判断元素是否为end,如果是,将p指向S的首元素,实现闭环。

第13行,把要出圈的元素的编码添加到结果链表中。

第14行~第16行,实现元素的移除和迭代。

时间复杂度和空间复杂度

基于上述伪代码,通过观察代码结构,我们可以发现,移除S中的一个元素所需要的时间主要依赖于这个元素的密码m。我们假定每一条指令的时间复杂度相同,且为t。我们使用tn来表示第n行代码需要的时间。那么整个程序的时间复杂度可以写为:

T = \sum_{i=1}^{n}t4+t5+(4*mi)t7+t12+t13+t14+t15+t16+t17

T = t\sum_{i=1}^{n}7+4mi

由于我们仅使用了链表,假设链表每个元素的空间复杂度为c,那么n个元素的空间复杂度就为cn。

所以整个程序的空间复杂度为cn+k;k为其它所有变量总的复杂度,是一个常量。

建立数据模型

接下来,根据上述分析的伪代码,采用C风格的代码来实现。

1.抽象编码和密码

struct Person {
    int number;
    int key;
};

2.定义节点结构

struct simple_node {
    Person person;
    simple_node* next;
    simple_node* last;
};

3.定义链表及其相关方法

struct simple_list {
    simple_node* finish;
    int count;
};

//初始化链表
void init_simple_list(simple_list* lplist) {
    lplist->finish = (simple_node*)malloc(sizeof(simple_node));
    //链表闭环
    lplist->finish->next = lplist->finish;
    lplist->finish->last = lplist->finish;
    lplist->count = 0;
}

//销毁链表
void destory_simple_list(simple_list* lplist) {
    for (simple_node* node = lplist->finish->next; node != lplist->finish; node = lplist->finish->next) {
        //解除 node 与 链表的联系
        lplist->finish->next    = node->next;
        node->next->last        = lplist->finish;
        //回收节点
        free(node);
    }
    //回收终止节点
    free(lplist->finish);
    lplist->count = 0;
}

//在指定位置添加元素
void simple_list_insert(simple_list* lplist, simple_node* pos, Person* lpPerson) {
    simple_node* targ_node = pos;
    //分配新内存
    simple_node* new_node = (simple_node*)malloc(sizeof(simple_node));
    //构造该节点的数据
    new_node->person.key    = lpPerson->key;
    new_node->person.number = lpPerson->number;
    //将新节点接入链表,默认插入在pos的前面
    new_node->next          = targ_node;
    new_node->last          = targ_node->last;
    targ_node->last->next   = new_node;
    targ_node->last         = new_node;
    //维护count
    lplist->count++;
}

//删除指定位置的元素
void simple_list_erase(simple_list* lplist, simple_node* pos) {
    simple_node* targ_node = pos;
    //接触该位置的节点与链表的联系
    targ_node->last->next = targ_node->next;
    targ_node->next->last = targ_node->last;
    //回收该节点
    free(targ_node);
    //维护count
    lplist->count--;
}

算法实现

int main()
{
    simple_list personlist;
    simple_list resultlist;
    init_simple_list(&personlist);
    init_simple_list(&resultlist);
    int recive = 0;
    int n = 0;
    Person person;
    while (scanf_s("%d", &recive)) {   
        n++;
        person.number = n;
        person.key = recive;
        simple_list_insert(&personlist, personlist.finish, &person);
    }

    simple_node* node = personlist.finish->next;
    while (personlist.count != 0) {
        int key = node->person.key;
        int n = 1;
        while (n < key) {
            n++;
            node = node->next;
            if (node == personlist.finish) //因为终止节点没有代表任何人,所以因该迭代到下一个节点,形成逻辑闭环
                node = node->next;
        }
        //将结果添加到结果列表中
        simple_list_insert(&resultlist, resultlist.finish, &(node->person));
        //删除该节点并向后迭代
        simple_node* tmp = node;
        node = node->next;
        if (node == personlist.finish)
            node = node->next;
         simple_list_erase(&personlist,tmp);
    }

    //输出结果
    for (simple_node* node = resultlist.finish->next; node != resultlist.finish; node = node->next)
        printf("%d  ", node->person.number);

    //一不要忘记回收数据
    destory_simple_list(&personlist);
    destory_simple_list(&resultlist);
}

完整代码

#include<stdio.h>
#include<stdlib.h>

struct Person {
    int number;
    int key;
};

struct simple_node {
    Person person;
    simple_node* next;
    simple_node* last;
};

struct simple_list {
    simple_node* finish;
    int count;
};

void init_simple_list(simple_list* lplist) {
    lplist->finish = (simple_node*)malloc(sizeof(simple_node));
    //链表闭环
    lplist->finish->next = lplist->finish;
    lplist->finish->last = lplist->finish;
    lplist->count = 0;
}

void destory_simple_list(simple_list* lplist) {
    for (simple_node* node = lplist->finish->next; node != lplist->finish; node = lplist->finish->next) {
        //解除 node 与 链表的联系
        lplist->finish->next    = node->next;
        node->next->last        = lplist->finish;
        //回收节点
        free(node);
    }
    //回收终止节点
    free(lplist->finish);
    lplist->count = 0;
}

void simple_list_insert(simple_list* lplist, simple_node* pos, Person* lpPerson) {
    simple_node* targ_node = pos;
    //分配新内存
    simple_node* new_node = (simple_node*)malloc(sizeof(simple_node));
    //构造该节点的数据
    new_node->person.key    = lpPerson->key;
    new_node->person.number = lpPerson->number;
    //将新节点接入链表,默认插入在pos的前面
    new_node->next          = targ_node;
    new_node->last          = targ_node->last;
    targ_node->last->next   = new_node;
    targ_node->last         = new_node;
    //维护count
    lplist->count++;
}

void simple_list_erase(simple_list* lplist, simple_node* pos) {
    simple_node* targ_node = pos;
    //接触该位置的节点与链表的联系
    targ_node->last->next = targ_node->next;
    targ_node->next->last = targ_node->last;
    //回收该节点
    free(targ_node);
    //维护count
    lplist->count--;
}

int main()
{
    simple_list personlist;
    simple_list resultlist;
    init_simple_list(&personlist);
    init_simple_list(&resultlist);
    int recive = 0;
    int n = 0;
    Person person;
    while (scanf_s("%d", &recive)) {   
        n++;
        person.number = n;
        person.key = recive;
        simple_list_insert(&personlist, personlist.finish, &person);
    }

    simple_node* node = personlist.finish->next;
    while (personlist.count != 0) {
        int key = node->person.key;
        int n = 1;
        while (n < key) {
            n++;
            node = node->next;
            if (node == personlist.finish) //因为终止节点没有代表任何人,所以因该迭代到下一个节点,形成逻辑闭环
                node = node->next;
        }
        //将结果添加到结果列表中
        simple_list_insert(&resultlist, resultlist.finish, &(node->person));
        //删除该节点并向后迭代
        simple_node* tmp = node;
        node = node->next;
        if (node == personlist.finish)
            node = node->next;
         simple_list_erase(&personlist,tmp);
    }
    //输出结果
    for (simple_node* node = resultlist.finish->next; node != resultlist.finish; node = node->next)
        printf("%d  ", node->person.number);
    //一不要忘记回收数据
    destory_simple_list(&personlist);
    destory_simple_list(&resultlist);
}

结语

最后,感谢大家阅读我的博客,如果这篇文章对你有帮助,请不要忘了为我点赞,你的鼓励是我持续创作的动力!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值