C语言写单向链表解决约瑟夫问题

C语言写单向链表解决约瑟夫问题

约瑟夫问题简介:

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

思路:

  1. 创建单向循环链表,(插入操作)并将尾指针指向首元结点,形成闭环

    (1)创建一个结点,用i来作为存放的数据和结点数的记录,s->data = i++;是将i先给到数据域,再进行自增操作

    s->data = i++; // 存放数据
    

    (2)p为尾指针,指向最后一个结点,将插入后结点s给到p,实现p的跟进

    p->next = s;  // 插入新结点
    p = s;  // 尾指针跟进
    

    (3)通过while循环,在循环中插入结点,判断是否已经创建完成指定长度的链表

    (4)创建完成,退出while,将尾结点的指针域指向首元结点,构成闭环

    s->next = head->next;  // 指向首元结点, 构成环
    

    (5)此时,头结点已经没有作用,只需要函数返回首元结点地址就可以

    free(head);  
    return s->next; // 返回首元结点的地址
    
  2. 杀死指定位置的结点(删除操作)

    (1)获取到要杀死的结点的前驱结点,方便删除

    // 获取要杀死的结点
    if( i < (death - 1)){
        p = p->next;
        i++;
    }
    

    (2)输出要杀死的结点的数据域数据

    printf("%2d->", p->next->data);
    

    (3)进行删除操作

    // 找到结点后杀死, 相当于删除结点
    temp = p->next;  // 保存前驱结点
    p->next = temp->next; // 链接到后继结点
    
    free(temp); // 释放结点
    

    (4) 通过循环去进行删除操作,循环判断语句为判断表是否为只剩最后一个结点,及为指针域指向自己,代码中通过p来进行链表操作,则在删除一个结点结束后,需要重新计数,在删除结束后通过将p = p->next;再进行循环,知道p == p->next;

2.代码实现

/*
 * @Author: xgh
 * @Date: 2020-06-14 08:33:35
 * @LastEditTime: 2020-06-14 09:51:37
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \VS-CODE-C\.vscode\JosephProblem\JosephProblem.c
 */ 
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;  // 数据域类型
#define ERROER 0 // 错误

typedef struct ClinkNode
{
    ElemType data; //数据域

    // 这里不能写成Node *next
    // 因为类型名的作用域是从大括号结尾开始,而在结构体内部不能使用,因为还没有定义
    // 需要声明
    struct ClinkNode *next;  // 指针域
}clinkNode;

clinkNode *create(int num);
void dealJoseph_01(int num, int death);

int main(void){

    int num = 41;  // 循环结点数
    int death = 3;  // 到达第几个结点先死

    dealJoseph_01(num , death);

    return 0;
}

/**
 * @description: 创建循环链表
 * @param {int num: 循环链表的结点数} 
 * @return: 首元结点的地址
 */
clinkNode *create(int num){

    if(num == 0){  // 数据错误
        exit(ERROER);
    }

    clinkNode *p = NULL, *head;
    int i = 1;  // 用于结点数, 也作为每个结点数据域存放的数据

    // 创建头结点
    head = (clinkNode *) malloc (sizeof(clinkNode));
    p = head;

    clinkNode *s; // 中间结点,用于存放生成插入的结点

    // 是否已经创建结束
    while(i <= num){
        // 创建一个新结点
        s = (clinkNode *) malloc (sizeof(clinkNode));

        s->data = i++; // 存放数据
        p->next = s;  // 插入新结点
        p = s;  // 尾指针跟进 
    }

    s->next = head->next;  // 指向首元结点, 构成环
    // 释放头结点, 有元素的结点已经构成一个环了,
    // 只需要返回结点地址就可以进行访问, 
    // 无需头指针,释放空间,减少资源占用
    free(head);  

    return s->next; // 返回首元结点的地址
}

/**
 * @description: 解决约瑟夫问题
 * @param {int num: 循环结点数} 
 * @param {int death: 到达第几个结点先死}
 * @return: NULL
 */
void dealJoseph_01(int num, int death){

    // 创建循环链表并接收
    clinkNode *p = create(num);
    clinkNode *temp;

    //解决约瑟夫问题
    printf("死亡顺序是:\n");
    int i =1;
    // 判断表是否只剩最后一个结点
    while(p != p->next){

        // 获取要杀死的结点
        if( i < (death - 1)){
            p = p->next;
            i++;
        }

        printf("%2d->", p->next->data);

        // 找到结点后杀死, 相当于删除结点
        temp = p->next;  // 保存前驱结点
        p->next = temp->next; // 链接到后继结点

        free(temp); // 释放结点

        p = p->next; // 重新开始计数        
    }

    // 最后剩下一个结点
    printf("\n最后剩下的是:%d\n", p->data);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值