循环链表
一。链表的建立
链表的尾结点指针域是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;
}
这个代码只是大概核心思想的体现,如果想要彻底弄明白,必须把上面两种算法搞懂才行,好了,以上就是我所介绍的内容。