本文是《小甲鱼数据结构》的学习笔记,对视频课程中的相关知识进行总结。
欢迎大家在评论区多多留言互动~~~~
1. 循环链表
对于单链表,由于每个结点只存储了向后的指针,到了尾部标识就停止了向后的操作,按照这种方式只能索引后继结点为不能索引前驱结点。这样会使得如果不从头结点出发,就无法访问全部结点。
为了解决这个问题可以使用单循环链表,将单链表中终端结点的指针端由空指针改为指向头结点,就使得整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表,如下图所示为两种循环链表
其中需要注意的是,这里并不是说循环链表一定要有头节点。其实循环链表的单链表的主要差异就在于循环的判断空链表的条件上,原来判断 head->next 是否为 null,现在则是 断 head->next 是否为 head。
终端结点用尾指针 rear 指示,则查找终端结点是 O(1),而开始结点是 rear->next->next ,当然也是 O(1)。
1.1 循环链表的初始化
循环链表的初始化部分的代码如下所示
/*初始化循环链表*/
void ds_init(node **pNode)
{
int item;
node *temp;
node *target;
printf("输入结点的值,输入0完成初始化\n");
while(1)
{
scanf("%d", &item);
fflush(stdin);
if(item == 0)
return;
if((*pNode) == NULL)
{ /*循环链表中只有一个结点*/
*pNode = (node*)malloc(sizeof(struct CLinkList));
if(!(*pNode))
exit(0);
(*pNode)->data = item;
(*pNode)->next = *pNode;
}
else
{
/*找到next指向第一个结点的结点*/
for(target = (*pNode); target->next != (*pNode); target = target->next)
;
/*生成一个新的结点*/
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
temp->next = *pNode;
target->next = temp;
}
}
}
1.2 循环链表的插入
/*链表存储结构的定义*/
typedef struct CLinkList
{
int data;
struct CLinkList *next;
}node;
/*插入结点*/
/*参数:链表的第一个结点,插入的位置*/
void ds_insert(node **pNode , int i)
{
node *temp;
node *target;
node *p;
int item;
int j = 1;
printf("输入要插入结点的值:");
scanf("%d", &item);
if(i == 1)
{ //新插入的结点作为第一个结点
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
/*寻找到最后一个结点*/
for(target = (*pNode); target->next != (*pNode); target = target->next)
;
temp->next = (*pNode);
target->next = temp;
*pNode = temp;
}
else
{
target = *pNode;
for( ; j < (i-1); ++j )
{
target = target->next;
}
// target指向第三个元素的
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
p = target->next;
target->next = temp;
temp->next = p;
}
}
1.3 循环链表的删除
/*删除结点*/
void ds_delete(node **pNode, int i)
{
node *target;
node *temp;
int j = 1;
if(i == 1)
{ //删除的是第一个结点
/*找到最后一个结点*/
for(target = *pNode; target->next != *pNode;target = target->next)
;
temp = *pNode;
*pNode = (*pNode)->next;
target->next = *pNode;
free(temp);
}
else
{
target = *pNode;
for( ; j < i-1; ++j)
{
target = target->next;
}
temp = target->next;
target->next = temp->next;
free(temp);
}
}
1.4 循环链表返回结点所在位置
/*返回结点所在位置*/
int ds_search(node *pNode, int elem)
{
node *target;
int i = 1;
for(target = pNode; target->data != elem && target->next != pNode; ++i)
{
target = target->next;
}
if(target->next == pNode) /*表中不存在该元素*/
return 0;
else
return i;
}
2. 用循环链表求解约瑟夫问题
2.1 问题描述
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
用循环链表模拟约瑟夫问题,把41个人自杀的顺序编号输出。
2.2 实现代码
//n个人围圈报数,报m出列,最后剩下的是几号?
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
int data;
struct node *next; //定义了结点,即数据和指向下一个结点位置的指针
}node;
node *create(int n)
{
// 根据参数 n 创建链表,即里面有多少个人。
node *p = NULL, *head;
head = (node*)malloc(sizeof (node )); //产生结点
p = head; //指向当前结点的指针,p是经常换的。
node *s;
int i = 1;
if( 0 != n )
{
while( i <= n )
{
s = (node *)malloc(sizeof (node)); //生成了一个结点,然后把他的地址给了 s
s->data = i++; // 为循环链表初始化,第一个结点为1,第二个结点为2。其中的 i 先赋值再相加。
p->next = s;
p = s; //将 s 结点又给了 p 使得 s 结点一直指向当前结点
}
s->next = head->next; //最后一个结点不是指向头结点,而是指向第一个结点,这样做的目的是去掉头结点而形成环。
}
free(head);
return s->next ;
}
int main()
{
int n = 41;
int m = 3;
int i;
node *p = create(n); //指向循环链表第一个结点的指针,存放在地址 p 里面
node *temp; //临时指针
m %= n; // m在这里是等于2
while (p != p->next )
{
for (i = 1; i < m-1; i++)
{
p = p->next ; //得到第二个结点
}
printf("%d->", p->next->data );
temp = p->next ; //将第三个结点赋值给 temp
p->next = temp->next ; //将第四个结点与第二个结点相连
free(temp); //删除第三个结点
p = p->next ;
}
printf("%d\n", p->data );
return 0;
}