循环单链表
如果有不足之处我会加以改正,都是个人理解难免有错误的地方请谅解。
由之前的单链表、双链表在到循环链表三个链表之间有很多重复和相似的地方。大体的逻辑关系是一致的,不同地方在于首位和每个节点之间的关系
单链表:每个节点之间依靠指针链接(方向为头->尾)头节点和尾节点->next均为null 双链表:每个节点之间有两个指针(方向为互相指向)头节点->prior和尾节点->next均为null
(单链表和双链表本质就是单链表)
循环链表:完全复刻单链表,唯一不同点在于尾节点->next=头节点
综上可知循环链表的实现完全复刻于单链表,除了对尾节点的处理不同之外
1>初始化单链表
和单链表相同,唯一不同在于将头节点指向头街(初始表头既是头节点又是尾节点)
void InitLinkList(LinkList &L)//初始化单链表,强调表所以用LinkList
{
L = (LNode*)malloc(sizeof(LNode));//为头节点开辟空间
if (L == NULL)
{
printf("为头节点分配空间失败!\n");
}
else {
L->next = L;//初始表头既是头又是尾
printf("初始化成功\n");
}
}
定义一个函数是否为空表
void Empty(LinkList L)
{
if (L->next == L)
printf("表头为空,分配成功\n");
}
2>对链表进行赋值
代码和单链表一致,唯一不同在于对尾节点处理,将尾节点找到将指针指向头节点即
void CreatList(LinkList &L)//输入数据
{
LNode *P, *D;//DATA是用来保持每次输入的数据,P的作用是指向单链表尾部
L = (LNode*)malloc(sizeof(LNode));//创建头节点
L->next = NULL;//初始化单链表,一开始为空表
P = L;//空表时头节点也是尾部节点
int n;//输入数据的个数
printf("请输入数据的个数:");
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
D = (LinkList)malloc(sizeof(LNode));//给D节点开辟一个空间
printf("data[%d]=", i);
scanf("%d", &D->data);//每次输入的数据到D
D->next = P->next;//第一个节点已经填充,将移动下一个节点
P->next = D;//D代表的是一个节点,P->next是指向下一个节点
P = D;
if (i==n - 1)
{
P->next = L;//尾节点指针指向头节点
}
}
}
在循环内部加一个判断是否是尾节点即可,将其指向头节点L(有没有L->next=NULL是一样的因为你赋值会将脏数据覆盖掉但是写上比较严谨)
对于循环尾节点的判断记住是判断(少数粗心写成=会有不同结果如下)
在输入第一个元素后会闪退直接输出
3>链表的输出
void ShowList(LinkList L)
{
LNode * P;
P = (LNode*)malloc(sizeof(LNode));//分配空间
P = L->next;//单链表从P点开始遍历,等同于从头节点开始
int i = 0;//记录位序
printf("打印单链表\n");
while (P != NULL&&P!=L)//只要节点不为空就可以继续输出
{
printf("data[%d]=%d\n", i, P->data);
i++;
P = P->next;
}
输出的逻辑在于输入的逻辑,唯一区别在于最后一步的处理将尾节点的指针指向表头。现在输出的逻辑的循环判断条件就要发生改变
分析:当变量到最后一个节点时候单链表的判断条件是(节点->next!=null)如果节点的下一个节点为空则跳出循环。现在对于循环链表除了(节点->next!=null)还需要对最后一个节点进行判断,由于节点的遍历是P=P->next所以动态节点每次指向的下一个节点注意是下一个节点。
个人经历:我第一次书写时候我的判断条件增加了P->next!=L,看起来没有问题但是循环内部的代码(P=P->next)已经将尾节点指向了头节点(L),所以如果这样判断的话,输出会有问题会少输出一个尾节点(如下图的错误代码的输出)
4>链表查找
单链表:如果相对节点进行查找,由于不具备随机查询的特征所以只有从头节点开始遍历整个节点知道找到需要节点
双链表:一样的原理和性质很好理解只是不像单链表对于节点前驱节点无法处理,双链表具有更高的灵活性比如(同时删除i节点和i+2和i-2节点),如果是单链表处理就要进行多次遍历(也可以一次完但是比较麻烦需要进行多次判断节点),而双链表只需要一次遍历即可解决。适用于不同的环境都各自有特点
循环链表:如果现在要求删除所有节点但是每次删除一个节点且都是尾节点,这样的条件下单链表和双链表删除一次就要遍历一次会浪费很多的时间和资源。但循环链表可以解决这个问题,对于频繁的对头尾处理的情况下可以将指针直接指向尾节点这样(尾节点->next就是头节点)会节省很多时间和资源。但是需要看情况而定,这里按常规查找元素
void GetElem(LinkList L, int i)
{
if (i < 0)
printf("查询位置不合法");
else {
LNode* P;//创建一个记录节点的指针
int number = 1;//记录查询到几个节点
P = L->next;//指向头节点,从头节点开始查询变量
if (i == 0)
printf("头节点为空");//头节点不存放数据
while (P != NULL && number < i)
{
P = P->next;//寻找到第i节点
number++;
if (P->next ==L)
{
P->next = L->next;
}
}
printf("查询第%d个元素为%d\n", i, P->data);
}
}
按位查找基本上和单链表的按位没有区别,我这里对尾节点进行了处理是为了体现循环链表。可以在尾节点进行一个判断如果遍历到了尾节点就可以指针指向第一个节点(头节点后第一个节点),其实这样的方法也可以用来对插入位置的合法进行判断处理。如果在没有获取链表长度情况下查询元素已经到尾节点还可以查询到就可以输出(超出链表范围无法查询)但是这个是多次一举的设计,没有直接获取链表长度可用性高同理
同理按值查询的原理也和单链表是一直的这里就省略了。
5>链表删除数据
删除数据代码和单链表一致,在表位处的处理只需令i-1节点指向L即可(p->next = q->next;)已经隐含了
删除第i个元素
步骤:首先我们需要找到第i-1个节点-->对i-1节点进行判断是否为尾节点(是:删除节点不在链表中,否:删除第i个节点 )-->删除第i个节点之后(将i-1节点指向头节点同时释放掉i节点)
void ListDelet(LinkList &L, int i)
{
if (i < 1)
printf("删除位置不合法");
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
int e=0;//用来保存删除数据
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)//这里是说明了i-1是尾节点
{
printf("删除内容为空");
}
else {
LNode*q = p->next;
e = q->data;//记录删除的数据
if (q->next = L)
{
p->next = L;
free(q);//释放掉该节点
}
else {
p->next = q->next;
free(q);//释放掉该节点
}
printf("删除第%d节点元素%d成功\n", i, e);
}
}
6>整体代码
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode
{
int data;//保存每个节点数据的变量
struct LNode *next;//指向下一个节点的指针
}LNode,*LinkList;
void InitLinkList(LinkList &L)//初始化单链表,强调表所以用LinkList
{
L = (LNode*)malloc(sizeof(LNode));//为头节点开辟空间
if (L == NULL)
{
printf("为头节点分配空间失败!\n");
}
else {
L->next = L;//初始表头既是头又是尾
printf("初始化成功\n");
}
}
void Empty(LinkList L)//检查表是否为空
{
if (L->next == L)
printf("表头为空,分配成功\n");
}
void CreatList(LinkList &L)//输入数据
{
LNode *P, *D;//DATA是用来保持每次输入的数据,P的作用是指向单链表尾部
L = (LNode*)malloc(sizeof(LNode));//创建头节点
L->next = NULL;//初始化单链表,一开始为空表
P = L;//空表时头节点也是尾部节点
int n;//输入数据的个数
printf("请输入数据的个数:");
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
D = (LinkList)malloc(sizeof(LNode));//给D节点开辟一个空间
printf("data[%d]=", i);
scanf("%d", &D->data);//每次输入的数据到D
D->next = P->next;//第一个节点已经填充,将移动下一个节点
P->next = D;//D代表的是一个节点,P->next是指向下一个节点
P = D;
if (i==n - 1)
{
P->next = L;//尾节点指针指向头节点
}
}
}
void GetElem(LinkList L, int i)
{
if (i < 0)
printf("查询位置不合法");
else {
LNode* P;//创建一个记录节点的指针
int number = 1;//记录查询到几个节点
P = L->next;//指向头节点,从头节点开始查询变量
if (i == 0)
printf("头节点为空");//头节点不存放数据
while (P != NULL && number < i)
{
P = P->next;//寻找到第i节点
number++;
if (P->next ==L)
{
P->next = L->next;
}
}
printf("查询第%d个元素为%d\n", i, P->data);
}
}
void ListDelet(LinkList &L, int i)
{
if (i < 1)
printf("删除位置不合法");
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
int e = 0;//用来保存删除数据
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)//这里是说明了i-1是尾节点
printf("删除内容为空");
LNode*q = p->next;
e = q->data;//记录删除的数据
if (p->next->next = L)
{
p->next = L;
free(q);//释放掉该节点
}
else {
p->next = q->next;
free(q);//释放掉该节点
}
printf("删除第%d节点元素%d成功\n", i, e);
}
void ShowList(LinkList L)
{
LNode * P;
P = (LNode*)malloc(sizeof(LNode));//分配空间
P = L->next;//单链表从P点开始遍历,等同于从头节点开始
int i = 0;//记录位序
printf("打印单链表\n");
while (P != NULL&&P!=L)//只要节点不为空就可以继续输出
{
printf("data[%d]=%d\n", i, P->data);
i++;
P = P->next;
}
}
void main()
{
LinkList L;//创建一个单链表
InitLinkList(L);//初始化
Empty( L);//判断是否为空
CreatList(L);//输入数据
GetElem(L, 7);//查询数据(按位)
ListDelet(L, 5);
ShowList(L);//输出数据
}