经典约瑟夫环问题(多种解法)

约瑟夫环(猴子选大王问题)

前言

本文是基于懒猫老师的数据结构课程所编写,我在这里直接给上地址:课程链接

1.循环链表实现

具体算法思想的文字图片描述后面补:…
可以去看懒猫老师课程·或者我下面代码中的笔记去理解

#include 
#include 
/*约瑟夫环可以联想成猴子选大王的问题,
 * 约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),
 * 从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。
 * 就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
 *
 */
//解决约瑟夫环主要有四种方式
//循环链表
struct NODE {
    int data;
    struct NODE* next;
};
int main() {
    int m, n;//猴子的个数n个 报数到m退出
    int i;
    int answer[100];//保存每一次的答案进行统一输出
    int count = 0;//用来控制题目答案的下标
    struct NODE *head, *tail, *p, *q;
    //分别定义一个首 尾节点,p指针指向当前处理的节点,q指针指向下一个节点
    //创建并初始化头结点,方便debug跟踪
    head = (struct NODE *) malloc(sizeof(struct NODE));
    head->data = -1;
    head->next = NULL;

    while (1) {
        scanf("%d%d", &n, &m);
        if (n == 0 || m == 0)//对输入进判断
        {
            free(head);
            break;
        } else {
            //尾插法创建循环链表
            tail = head;
            for (i = 0; i < n; i++) {
                p = (struct NODE *) malloc(sizeof(struct NODE));
                p->data = i + 1;//从0开始循环所以加1代表第一个猴子的报数
                tail->next = p;//插到尾部
                p->next = head->next;//保持首尾相接
                tail = p;//tail向后移动到表尾
            }
            if(n>0)
            {
                tail->next = head->next;
            }
            //到这里环形就构建完毕了,开始解决问题
            //把p重新移动到首元节点处,将q放在表尾 准备进行删除操作
            p = head->next;
            q = tail;
            i = 1;
            // 即保持pq一前一后的关系不变,p指向当前要处理的节点,q指向其前面的一个结点
        }
        while (p != q)//当表中只有一个元素的时候q p指针就会重合,此时,正好指向的就是我们要的猴王
        {//删除结点
            if (i == m) {
                q->next = p->next;
                free(p);
                p = q->next;//将p移动到下一个节点
                i = 1;//每次有人出去的时候 下一个人都需要重新报数
            } else {
                i++;
                //p q分别向后移动 而且始终保持q在p之前
                q = p;
                p = p->next;
            }
        }//跳出while循环说明此时p=q 猴王就是p->data
        //注意:当链表中只有两个元素时,删除的是首元节点,如果需要保持链表整性的话
        // 那么需要将头结点接到现在留下来的唯一节点上 即head->next=q
        answer[count] = p->data;
        count++;
        free(p);
        head->next = NULL;//
    }
    for (int j = 0; j < count; j++) {
        printf("%d\n", answer[j]);
    }
   // free(head);可以不要 因为在最初时就释放了
    return 0;
}

2.数组标志位实现

其实后面两种方法也是借助了链表的思想,进行模拟,只不过虽然本质上是数组,但是却用了取余的方式巧妙的转换成了类似一个循环链表,类似的操作在顺序栈和队列中也有用到,如果不理解的朋友可以去看王卓老师的数据结构视频,十分详细


#include 
//方法二
//数组标志位实现
/**
 * 主函数
 * 本函数通过模拟猴子报数的过程,确定最后剩下的猴子的编号
 * 输入: n - 猴子的总数
 *       m - 报数的上限
 * 输出: 最后剩下的猴子的编号
 */
int main() {
    int n,m;
    int number;//记录剩余猴子个数
    int count=1;//代表当前报数
    int i,pos;
    // 循环读取输入的猴子总数n和报数上限m
    while (~scanf("%d %d",&n,&m))
        // 如果输入的n或m为0,则结束程序
        if(n==0 || m==0)
        {
            return 0;

        }
    number=n;
    // 创建一个monkey数组存储猴子的编号和状态
    // 存储-1就代表猴子已经出局了,1到n+1代表还没退出的猴子
    int monkey[300]={0};
    // 初始化monkey数组,设置每个猴子的初始编号
    for(i=1;i<n;i++)
    {
        monkey[i]=i+1;
    }
    // 主循环,模拟报数过程,直到只剩下一只猴子
    while(number>1)
    {
        // 如果当前猴子还在游戏中(未出局)
        if(monkey[pos]>0)
        {
            // 如果当前报数还未达到m,则继续报数
            if(count!=m) {
                count++;//报数加1
                pos = (pos + 1) % n;//就像一个圈圈
                // 一般是往右移动一位 当圈圈到头了 就从0开始
            }else{
                // 如果当前报数达到m,则当前猴子出局
                monkey[pos]=0;
                number--;
                count=1;
                pos=(pos+1)%n;
            }
        } else
        {
            // 如果当前猴子已经出局,则移动到下一个猴子
            pos=(pos+1)%n;
        }
    }
    // 遍历monkey数组,找到并打印最后剩下的猴子的编号
    for (i = 0; i <n; i++)
    {
     if(monkey[i]>0)
     {
         printf("%d\n",monkey[i]);
     }
    }
    return 0;
}


数组链接方式实现

也是借助链表的思想,有点类似于双指针,后面会补上 未完待续…

你可能感兴趣的:(数据结构,算法)