循环链表的定义
将单链表中最后一个数据元素的next指针指向第一个元素,即把链表的两头连接,形成了一个环状链表,称为循环链表
在循环链表中可以定义一个“当前”指针,称为游标,通过游标来遍历链表中所有元素;
循环链表和动态链表相比,唯一的不同就是循环链表首尾相连,其他都完全一样
可以用两次打印循环链表,直观看下是否为循环链表
循环链表的应用-约瑟夫环问题
约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(以编号1,2,3,…,n分别表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,要求找到最后出列的那个人。
例如:n=7;从k=1开始顺时针报数,数到m=3的那个人出列 ->3出列
#include<stdio.h>
#include<malloc.h>
typedef struct node{
int number;
struct node *next;
}person;
循环链表初始化
person *initLink(int n){
person *head = (person *)malloc(sizeof(person)); //结构体头指针分配内存空间
head->number =1; //数据域值1
head->next = NULL; //指针域指向NULL
person *cyclic = head; //head首地址赋值给cyclic
int i;
for(i=2;i<=n;i++){
person *body = (person *)malloc(sizeof(person)); //添加结点
body->number =i; //数据域赋值
body->next = NULL; //指针域指向NULL
cyclic->next = body; // 将新建结点添加到圆
cyclic = cyclic->next;
}
cyclic->next = head; //首尾相连
return head;
}
找打该元素并出列
void findAndOut(person *head,int k, int m){
person *tail = head;
printf("head %d\n", tail->number);
//找到链表第一个结点的上一个结点,为删除操作做准备
while(tail->next != head){
tail = tail->next;
}
person *p =head;
//找到编号为k的人
while(p->number !=k){
tail = p;
p=p->next;
}
//从编号为k的人开始,只要符合条件p->next == p时,说明链表中除了p结点,所有编号都出列了
while(p->next !=p){
//找到从p报数1开始,包m的人,并且还要知道m-1的人的位置tail,方便做删除操作。
int i;
for(i=1;i<m;i++){
tail = p;
p=p->next;
}
tail ->next = p->next; //从链表上将p结点摘下来
printf("出列人的编号为:%d\n",p->number);
free(p);
p = tail->next;//继续使用p指针指向列出编号的下一个编号,游戏继续
}
printf("出列人的编号为:%d\n",p->number);
free(p);
}
完整代码
#include<stdio.h>
#include<malloc.h>
typedef struct node{
int number;
struct node *next;
}person;
person *initLink();
void findAndOut();
void display();
int main(){
printf("输入圆桌上的人数:\n");
int n;
scanf("%d", &n);
person *head = initLink(n);
printf("从第k人开始报数(k>1且k<%d):",n);
// display(head);
int k;
scanf("%d", &k);
printf("数到m的人出列:\n");
int m;
scanf("%d",&m);
findAndOut(head,k,m);
}
person *initLink(int n){
person *head = (person *)malloc(sizeof(person)); //结构体头指针分配内存空间
head->number =1; //数据域值1
head->next = NULL; //指针域指向NULL
person *cyclic = head; //head首地址赋值给cyclic
int i;
for(i=2;i<=n;i++){
person *body = (person *)malloc(sizeof(person)); //添加结点
body->number =i; //数据域赋值
body->next = NULL; //指针域指向NULL
cyclic->next = body; // 将新建结点添加到圆
cyclic = cyclic->next;
//printf("cyclic:%d \n", cyclic->number);
}
printf("cyclic:%d \n", cyclic->number);
cyclic->next = head; //首尾相连
return head;
}
//链表显示
void display(person *p){
person* temp = p; //将temp指针重新指向头结点
//只要temp指针指向的结点next不是NULL,就执行输出语句。
while(temp->next)
{
temp = temp->next; //这里指向首元结点,头结点一般为空
printf("%d ",temp->number);
}
printf("\n");
}
void findAndOut(person *head,int k, int m){
person *tail = head;
printf("head %d\n", tail->number);
//找到链表第一个结点的上一个结点,为删除操作做准备
while(tail->next != head){
tail = tail->next;
}
person *p =head;
//找到编号为k的人
while(p->number !=k){
tail = p;
p=p->next;
}
//从编号为k的人开始,只要符合条件p->next == p时,说明链表中除了p结点,所有编号都出列了
while(p->next !=p){
//找到从p报数1开始,包m的人,并且还要知道m-1的人的位置tail,方便做删除操作。
int i;
for(i=1;i<m;i++){
tail = p;
p=p->next;
}
tail ->next = p->next; //从链表上将p结点摘下来
printf("出列人的编号为:%d\n",p->number);
free(p);
p = tail->next;//继续使用p指针指向列出编号的下一个编号,游戏继续
}
printf("出列人的编号为:%d\n",p->number);
free(p);
}
循环链表插入元素
1)普通插入元素(和单链表一样)
2)尾插法:(和单链表一样,单链表写法支持尾插法;因为辅助指针后跳length次,指向最后面那个元素)
在尾部插入结点,将新增结点的next域指针指向头结点;
3)头插法
(要进行头插法,需要求出尾结点,与单链表不一样,保证是循环链表)
第一次插入元素是,让游标指向0号结点
新增结点A插入在头结点之后,头结点的next指针指向新增结点A,A指向“首元结点”,表尾结点指向新增结点A;
完成插入操作之后,新增结点A变成了新的首元结点;
4)第一次插入元素
此时链表为空,只有一个头结点(一般头结点不辅助),新增结点为首元结点;
头结点next域指针指向首元结点,
line *head =NULL;
head = (line *)malloc(sizeof(line));
head->next = NULL;
line *temp =head; //遍历结点
line *body = (line *)malloc(sizeof(line));
body->number = 1;
body->next = NULL;
temp->next = body;
body->next = body;
循环链表删除元素
1)删除普通结点
2)删除头结点(删除0号位置处元素),需要求出尾结点
循环链表优缺点:
优点:功能增强,在单链表的基础做了一个加强,可以取代单链表使用;其next和Current操作能高效的遍历链表中所有元素;
缺点:代码复杂度变高;(有吗,相比于单链表,循环链表是把单链表首尾相连了。。)