循环链表及约瑟夫环问题

循环链表

一。链表的建立
链表的尾结点指针域是NULL.而循环链表的建立,尾结点指向头结点

void CreatByRear(LinkList head)
{
   Node*r,*s;
   char name[20];
   int number;
   r=head;
   printf("请输入学生的姓名和学号:\n");
   while(1)
   {
   scanf("%s",name);
   scanf("%d",&number);
   if(number==0)
      break;
    s=(Node*)malloc(sizeof(Node));
    strcpy(s->name,name);
    s->number=number;
    r->next=s;
    r=s;
   }
   r->next=head;

链表结尾的判断
单链表判断结点是否为表尾结点,只需判断结点的指针域是否为NULL(p->next==NULL),如果是,则为尾结点,否则不是,而循环链表判断是否为尾结点,则是判断该结点的指针域是否指向链表头结点(p->next=head)。
好了,看了上述的循环链表的介绍,是不是觉得他很简单呢,现在我们准备一下实战模拟。
在模拟之前,给大家一个思考题。
问题:有n个小朋友,按1,2,…,n编号做成一圈,从第一个开始报数,报至m的退出,从下一个开始,继续从一开始报数,报道m的退出,如此反复,最后给剩下的一个小朋友发奖品,编写程序,输出获得奖品的小朋友的序号。
对于还没接触链表的友友们,估计都会选择用数组来解决这个问题,既然都提到这个了,那就说一下它的解法吧
一。数组
用一个数组来存放这n个编号,

arr[6]={1,2,3,4,5,6}

然后不停着遍历,对于被选中的编号,我们就做一个标记,例如编号arr[2]=3被选中了,那么我们可以做一个标记,例如让arr[2]=-1,来表示arr[2]存放的编号已经出局的了。

arr  1 2 3 4 5 6
         .
         .第一个出局的人
         .
     1 2 -1 4 5 6

然后就按照这种方法,不停遍历数组,不停做标记,知道数组中只有一个元素是非-1的。
感觉思路是不是挺简单的,但编码过程还是很考验人的。
注意一下,这种做法的时间复杂度是O(n*m),空间复杂度是O(n);
下面是核心代码。

int cnt=0,i=0,k=0;
while(cnt!=n) //因为要求n个人的出局顺序,因此cnt为达到n时,需要不断循环
{
   i++;
   if(i>n)
     i=1;
   if(a[i]==0)
  {
    k++;
    if(k==m)
      {
      a[i]=1;
      cnt++;
      printf("%d",i);
      k=0;
      }
   }
}

好的,以上介绍后是不是对约瑟夫环有了很好的认知,现在让我们用循环链表来解决这个问题。
结构体代码:

typedef struct node
{
   int data;
   struct node*next;
 }

链表的创建相信应该都会,而且在上面我已经写了循环链表的创建过程,所以下面就直接上核心代码了

while(p->next!=p)//如果p->next=p,说明当前只有一个元素
{
    for(int i=1;i<m;i++)
    {
       r=p;//保留出局的前一个代码
       p=p->next;
     }
     printf("%d",p->data);
     r->next=p->next;
     p=p->next;
 }

在这里插入图片描述

下面是完整代码

#include<bits/stdc++.h>
using namespace std;
//用链表实现约瑟夫环问题 (循环链表) 
 
typedef struct node  //typedef用来重命名struct node这种数据类型,将其命名为Node 
{
 int data;
 struct node* next;
}Node;
 
void ysflb(int N,int M)  //总共有N个人,报到数字为M的人出局 
{
 //初始化循环链表
 Node *head = NULL,*p=NULL,*r=NULL;   //head为头指针,指向链表的第一个结点,一开始赋值为NULL,代表不指向任何结点 
 head = (Node*)malloc(sizeof(Node));  //让head指向一个实际的空间
 if(NULL==head)  //内存空间可能会申请失败,大多数情况不会申请失败 
 {
   cout<<"Memory Failed!";
   return;
 } 
 head->data=1;       //从1开始编号 
 head->next=NULL;    //一开始整个链表只有一个Node(结点),这个Node有两个域,分别是data和next
                     //data从1开始,next指向NULL,总共需要N个结点,现在创建了一个,还需要N-1个 
    p=head;             //head要保持不能改变,才能够找到链表的起始位置,一开始p也指向第一个结点
                     //p等一下会被使用,用它可以便于创建剩下的N-1个结点 
       
 //尾插法创建链表,已经有一个1号结点了,还需要创建剩下的n-1个结点 
 for(int i=2;i<=N;i++)
 {
  r=(Node*)malloc(sizeof(Node)); 
  r->data=i;
  r->next=NULL;
  //插入结点 
  p->next=r;
  p=r;
  
 }
 //创建循环链表
 p->next=head;   //最后一个结点的next指向头结点
 p=head;         //为后续方便,将p指向头结点
 
 //约瑟夫环的模拟
 while(p->next!= p)  //如果p的next=p,说明目前只有一个元素 
 {
  for(int i=1;i<M;i++)  //报到数字为M的时候出局 
  {
     r=p;   //保留出局的前一个结点 
     p=p->next; //p指向的是要出局的这个结点,需要保留前一个结点
  }
  // 输出
  cout<<p->data<<" ";
  r->next=p->next;    //删除p的目的,此时p指向哪里?  :  
  p=p->next;  //更新p重新进行报数 
 } 
 cout<<p->data; 
}
 
int main()
{
 ysflb(10,3);
 return 0;
} 

当然除了这两个,还有另一种算法,叫递归,递归算法的代码十分简短,但他的思想却是很难得,在这就给你们粗略看下大致思想

int ysfh(int n,int m,int i)//有n个人,报到数字m时出局,求第i个人出局的编号
{
  if(i==1)
     return(m-1+n)%n;
  return (ysfh(n-1,m,i-1)+m)%n;
}

这个代码只是大概核心思想的体现,如果想要彻底弄明白,必须把上面两种算法搞懂才行,好了,以上就是我所介绍的内容。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值