目录
- 题目
- 解答
- 法一:单循环链表
- 法二:循环数组
- 法三:递归
- 法四:迭代
- 总结
题目

解答
法一:单循环链表
#include<stdio.h>
#include<stdlib.h>
//定义单循环链表数据结构
typedef struct CNode{
int data;
struct CNode *next;
}CNode;
typedef CNode *CLinkList;
//初始化一个指向头节点的指针,使头指针->next=头指针,头指针->data不做处理
void InitList_L(CLinkList *L){
(*L) = (CLinkList)malloc(sizeof(CNode));
if(!(*L))
exit(-2);
(*L)->next = *L;
}
//头插法建立单循环链表
int CreateList_HL(CLinkList *L,int s[],int n){ //二级指针
int i;
CLinkList p;
*L = (CLinkList)malloc(sizeof(CNode));
if(!(*L))
exit(-2);
(*L)->next = *L; //只有一个头节点head,就让next指向自己
for(i=0; i<n; i++){
p = (CLinkList)malloc(sizeof(CNode));
if(!p)
exit(-2);
p->data = s[i]; //录入数据
p->next = (*L)->next; //将头指针所指向的下一个结点的地址,赋给新创建结点的next
js (*L)->next = p; //将新创建的结点的地址赋给头指针的下一个结点
}
return 1;
}
//尾插法建立单循环链表
int CreateList_TL(CLinkList *L,int s[],int n){
int i;
CLinkList p, q;
*L = (CLinkList)malloc(sizeof(CNode));
if(!(*L))
exit(-2);
(*L)->next = *L; //只有一个头节点head,就让next指向自己
for(i=0,q=*L; i<n; i++){
p = (CLinkList)malloc(sizeof(CNode));
if(!p)
exit(-2);
p->data = s[i]; //录入数据
q->next = p;
q = q->next;
}
q->next = *L; //最后一个结点指向head
return 1;
}
//求单循环链表的长度
int ListLength(CLinkList L){
CLinkList p;
int count;
if(L){
count = 0;
p = L; //p指向头结点
while(p->next!=L){ //p没到表头
count++;
p = p->next;
}
}
return count;
}
//得到指向单循环链表第i个元素的指针
CLinkList GetElemPtr(CLinkList L, int i){
int count;
CLinkList p;
if(L&&i>0){
count = 1;
p = L->next;
while(p!=L && count<i){
count++;
p = p->next;
}
if(p!=L) //L为头指针
return p;
}
return NULL;
}
//单循环链表第i个位置插入元素e
int ListInsert(CLinkList L, int i, int e){
CLinkList p, s;
int j;
if(i<1 || i>ListLength(L)+1) //插入位置不合理
return 0;
p = L;
j = 0;
while(j<i-1){ //寻找第i-1个结点
p = p->next;
++j;
}
s = (CLinkList)malloc(sizeof(CNode));
if(!s)
exit(-2);
s->data = e;
s->next = p->next;
p->next = s;
return 1;
}
//单循环链表第i个位置删除元素e
int ListDelete(CLinkList L, int i, int *e){
CLinkList pre, p;
int j;
if(i<1 || i>ListLength(L)) //删除位置不合理
return 0;
pre = L;
j = 1;
while(pre->next && j<i){ //寻找第i个结点,并令pre指向其前驱
pre = pre->next;
++j;
}
p = pre->next;
pre->next = p->next;
*e = p->data;
free(p);
p=NULL;
return 1;
}
//遍历单循环链表
void ListTraverse(CLinkList编程 L){
CLinkList p;
p = L->next; //p指向头结点,正向访问链表
while(p!=L){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
}
//判断单循环链表是否为空
int ListEmpty(CLinkList L){
if(L!=NULL && L->next==L) //单循环链表存在并且只有头结点
return 1;
else
return 0;
}
/*
用单循环链表解决约瑟夫环问题(带头结点)
这里的单循环链表使用了带头结点的,方便找到表头的位置,但是由于头结点不存储元素,因此需要跳过头结点
链表插入删除都比较方便,不需要移动大量元素,只需要移动指针即可,适合这一类问题
*/
void Algo(CLinkList L,int k,int m){ //从编号为k的人开始数,数到m的那个人出队列
int count,i,j; //count表示每次从1开始数,i用来找编号为k的结点的前驱
CLinkList pre,p;
pre=L;
count=1,i=1,j=1;
/*寻找第k个结点,并令pre指向其前驱*/
while(i<k){
if(pre->next==L) //跳过头结点
pre=pre->next->next;
else
pre = pre->next;
++i;
}
while(L->next!=L){ //如果单循环链表不为空
if(count==m){
/*下一个结点不是头结点,直接删除*/
if(pre->next!=L){
p=pre->next;
printf("第%d次出环的元素是%d\n",j++,p->data);
pre->next=p->next;
free(p);
p=NULL;
count=1;
}
/*下一个结点是头结点,下下个结点不是头结点,跳过头结点,删除下下个结点(头结点不保存数据,因此不参与运算)*/
else if(pre->next->next!=L){
p=pre->next->next;
printf("第%d次出环的元素是%d\n",j++,p->data);
pre->next->next=p->next;
free(p);
p=NULL;
count=1;
}
/*下一个结点是头结点,下下个结点也是头结点,说明单循环链表已经为空了,只剩下头结点,因此跳出循环*/
else
break;
}
count++; //count代表要删除的结点数的数,始终在pre指向的结点之前
if(pre->next==L) //跳过头结点
pre=pre->next->next; //pre指向要删除结点的前一个结点
else
pre = pre->next; //pre指向要删除结点的前一个结点
}
}
void main(){
CLinkList L;
int n=5,s[5]={1,2,3,4,5}; //假设5个人围坐一圈
int k=3,m=2; //第一次从编号为k的人开始数,数到m的那个人出队列
CreateList_TL(&L,s,n); //头插法建立单循环链表
ListTraverse(L); //遍历原始队列
printf("假设第一次从编号为%pythond的人开始数,数到%d的那个人出环\n",k,m);
Algo(L,k,m); //用单循环链表解决约瑟夫环问题(带头结点)
}
法二:循环数组
/*
用循环数组解决约瑟夫环问题
解决问题的难点有两个:
1、如何求下一个的人的位置:在循环数组中向后移动采用:(k+l)%n
2、此人出圈后对这个人的位置如何处理:先将出圈人的位置打印输出,然后将其位置元素设置为0,表示它已出圈,以后见到它就直接跳过去
*/
void Algo2(int s[],int n,int k0,int m){ //开始一共有n个人,从第k0个人开始数,数到m的那个人出队列
int i,j,k=k0-1; //元素访问的下标为 k,初始时从第k0个人开始数
for(i=0;i<n;i++){ //总共循环n次,每循环一次,出队一个元素,k在循环中会不断的运动
j=1; //j表示数的数,初始为1
while(j<m){ //如果还没有数到m
while(s[k]==0) //如果s[k]为0,证明它已经出圈了,则跳过它
k=(k+1)%n;
j++; //数下一个数
k=(k+1)%n; //数组下标向后走一步
}
while(s[k]==0) //如果数到m发现它出圈了,则跳过它,找到第一个没出圈的数
k=(k+1)%n;
printf("第%d次出环的元素是%d\n",i+1,s[k]); //先将出圈人的位置打印输出
s[k]=0; //然后将其位置元素设置为0,表示它已经出圈
}
}
void main(){
int n=5,s[5]={1,2,3,4,5}; //假设5个人围坐一圈
int k=3,m=2; //第一次从编号为k的人开始数,数到m的那个人出队列
printf("假设第一次从编号为%d的人开始数,数到%d的那个人出环\n",k,m);
Algo2(s,n,k,m); //用循环数组解决约瑟夫环问题
}
法三:递归
/javascript*
用递归解决约瑟夫环问题
n指的是总人数,m指的是每次最大报到的数值,i是第i次,该函数每次可以求出第i次扔海里的人的编号
*/
int Algo3(int n,int mjs,int i){
if(i==1)
return (n+m-1)%n;
else
return (Algo3(n-1,m,i-1)+m)%n;
}
void main(){
int n=5; //假设5个人围坐一圈
int m=2; //数到2的那个人出环
printf("假设第一次从编号为1的人开始数,数到%d的那个人出环\n",m);
for(int i=1;i<=n;i++)
printf("第%d次出环的元素是%d\n",i,Algo3(n,m,i)+1); //因为求出的结果是数组中的下标,最终的编号还要加1
}
法四:迭代
/*
用迭代解决约瑟夫环问题
递推公式:
f(N,M)=(f(N−1,M)+M)%N
f(N,M)表示,N个人报数,每报到M时杀掉那个人,最终胜利者的编号
f(N−1,M)表示,N-1个人报数,每报到M时杀掉那个人,最终胜利者的编号
*/
int Algo4(int n,int m){
int i,p=0;
for(i=2;i<=n;i++){
p=(p+m)%i;
}
return p+1; //因为求出的结果是数组中的下标,最终的编号还要加1
}
void main(){
int n=5; //假设5个人围坐一圈
int m=2; //数到2的那个人出环
printf("假设第一次从编号为1的人开始数,最后出环的是:%d\n",Algo4(n,m));
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
加载中,请稍侯......
精彩评论