单向循环链表--约瑟夫问题

约瑟夫问题是一个经典的计算机科学和数学问题,也被称为约瑟夫环或丢手绢问题。

约瑟夫问题起源于一个历史故事,据说古罗马历史学家弗拉维奥·约瑟夫斯和他的40个战友被罗马人围困在一个洞里,他们决定自杀而不是被俘,于是排成一个圆圈,每数到第三个人时,该人就必须自杀,直到所有人都死去。约瑟夫斯和他的朋友并不想遵从这一决定,通过巧妙的安排自己和朋友的位置逃脱了死亡。在数学和计算机编程中,约瑟夫问题的一般形式是:N个人围成一圈,从某个人开始报数,报到M的人出圈,然后从下一个人继续报数,直到全圈的人都出圈为止;

问题:

设编号分别为:1 2 n n 个人围坐一圈。约定序号为 k 1 k n )的人从 1 开始计数,数到 m 的那 个人出列,他的下一位又从1 开始计数,数到 m 的那个人又出列,依次类推,直到所有人出列为止。
假设:设 n=8 ,M =3 ,k =4 时,出列序列为: (6 2 7 4 3 5 1 8)

求解问题:

大家可以用一个不带头结点的循环链表来处理 问题:先构成一个有 n 个结点的单向循环链表,然后从第k 结点起从 1 计数,计到 m 时,对应结点从链表中删除;然后再从被删除结点的 下一个结点起又从1 开始计数 …… ,直到所有结点都出列时算法结束。

a.创建单向循环链表,并在数据域中存入相应的编号(1....n)

//创建空链表
linklist_t *creat_Nodehead()
{
        linklist_t *Nodehead=(linklist_t *)malloc(sizeof(linklist_t));
        if(Nodehead==NULL)
        {
                printf("头结点开辟失败\n");
                return NULL;
        }
        memset(Nodehead,0,sizeof(linklist_t));
        Nodehead->data=1;
        Nodehead->next=Nodehead;
        return Nodehead;
}
//创建新结点
linklist_t  *creat_NewNode()
{
        linklist_t *Node=(linklist_t *)malloc(sizeof(linklist_t));
        if(Node==NULL)
        {
                printf("结点开辟失败\n");
                return NULL;
        }
        memset(Node,0,sizeof(linklist_t));
        return Node;
}
//在链表尾部添加
void link_endNode(linklist_t *list,int i)
{
        linklist_t *NewNode=creat_NewNode(i);
        NewNode->data=i;
        linklist_t *tmp=list;
        while(tmp->next!=list)
        {
                tmp=tmp->next;
        }
        tmp->next=NewNode;
        NewNode->next=list;
}

b.找到起始位置

使用while进行死循环遍历单向循环链表,若tmp这个结点的数据域所存编号与M相同,则跳出循环,当前结点位置就是起始位置1.

  while(1)
  {
          if(tmp->data==M)
          {
                break;
          }
          tmp=tmp->next;
   }

c.遍历单向循环链表,并对m计数

设置一个计数器flag,若flag==K-1;则表示下一个结点为出链表的结点,将这个节点删除,然后将flag重新置为1,继续进行上述操作,直到所有结点删除完(所有人出列)

while(N--)
        {
                while(1)
                {
                        if(K-1==flag)
                        {
                                break;
                        }
                        flag++;
                        tmp=tmp->next;
                }
                flag=1;
                linklist_t *freeNode=tmp->next;
                number=tmp->next->data;
                tmp->next=tmp->next->next;
                tmp=tmp->next;
                free(freeNode);
                freeNode=NULL;
                printf("%d-->",number);
        }
        putchar('\n');
}

结果展示: 

在 博客上有我写的代码的压缩包,有兴趣的同学可以下载下来自己运行。注:(我的代码是在Linux ——Ubuntu中编写的)

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是单向循环链表约瑟夫问题的代码实现: ``` #include <iostream> using namespace std; // 定义单向循环链表节点 struct Node { int data; // 数据域 Node* next; // 指针域 Node(int val = 0) : data(val), next(nullptr) {} // 构造函数 }; // 定义单向循环链表类 class CirLinkedList { public: CirLinkedList(); // 构造函数 ~CirLinkedList(); // 析构函数 void CreateList(int n); // 创建长度为n的单向循环链表 void Josephus(int m); // 约瑟夫问题,每m个节点删掉一个 private: Node* first; // 链表头指针 }; CirLinkedList::CirLinkedList() { first = nullptr; // 初始化链表为空 } CirLinkedList::~CirLinkedList() { // 释放链表内存 Node* temp = first; while (temp) { Node* p = temp; temp = temp -> next; delete p; } } void CirLinkedList::CreateList(int n) { if (n < 1) return; first = new Node(1); // 创建第一个节点 Node* temp = first; // 创建剩余的n-1个节点 for (int i = 2; i <= n; ++i) { Node* p = new Node(i); temp -> next = p; temp = temp -> next; } temp -> next = first; // 最后一个节点指向头结点,形成单向循环链表 } void CirLinkedList::Josephus(int m) { // 如果链表为空,直接退出 if (first == nullptr) return; Node* pre = nullptr; // pre指向待删除节点的前一个节点 Node* cur = first; // cur指向待删除节点 // 找到待删除节点,确保链表中至少有两个节点 while (cur -> next != cur) { // 移动m-1步,找到待删除节点 for (int i = 0; i < m - 1; ++i) { pre = cur; cur = cur -> next; } pre -> next = cur -> next; // 将待删除节点从链表中删除 cout << cur -> data << " "; // 输出删除的节点 delete cur; // 释放删除的节点内存 cur = pre -> next; // 更新待删除节点为下一个节点 } // 输出链表剩余节点 cout << cur -> data << endl; } int main() { int n, m; cin >> n >> m; // 输入链表长度n和每次删掉的节点数m CirLinkedList cll; cll.CreateList(n); // 创建单向循环链表 cll.Josephus(m); // 解决约瑟夫问题 return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值