1.定义:是一种头尾相接的单链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)
图解如下:
单循环链表为空表时:其头结点的next域指向它自己,即头结点next等于头指针
2.单循环链表的优劣
1.优点:
<1>节省内存空间:单循环链表的最后一个结点指向头结点,因此不需要使用额外的标志或指针来表示链表的结束位置,相对于普通单链表,节省了空间。
<2>方便链表的循环处理: 因为最后一个结点的 next 指针指向链表的头结点,任何结点都可以作为起点进行循环处理,特别适合处理需要循环操作的数据结构,比如约瑟夫环问题。
<3>易于从任意结点遍历: 在循环链表中,可以从任意结点开始遍历整个链表,这对于某些特定的算法非常方便。
<4>适合队列的环形缓冲区实现: 单循环链表的结构天然适合用来实现环形缓冲区,这在一些队列的实现中很常见。
2.缺点:
<1>不易处理链表的结束条件: 由于链表是循环的,没有明确的终止标志,某些操作(如判断链表长度)可能会复杂一些,需要额外判断条件以避免无限循环。
<2>实现和维护较复杂: 由于循环结构的特殊性,某些操作(如插入和删除)需要小心处理边界情况,代码实现和调试可能比普通单链表更复杂。
<3>不易找到尾结点: 如果没有维护一个尾指针,那么从头结点遍历到尾结点需要遍历整个链表。维护尾指针也增加了复杂性。
<4>可能造成死循环: 如果处理不当,比如误将链表设为循环或未正确处理边界条件,容易引发死循环,导致程序无法正常结束。
3.单循环链表的注意事项
由于循环链表中没有 NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断 p 或p->next 是否为空,而是判断它们是否等于头指针。
4.带尾指针的单循环链表:
表的操作常常是在表的首尾位置上进行,为了查找操作的方便于是就有了带尾指针的单循环链表
5.单循环链表的整表创建及相关操作
1.单循环链表的结构定义:
与单链表的结构定义完全一致
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
#define bool int
#define false 0
#define true 1
//1.单循环链表的结构定义不变
typedef struct LNode
{
ElemType data;//数据域
struct LNode* next;//指针域
}LNode, * LinkHead,* LinkTail;
//LNode *用来定义结点指针;
//LinkHead 用来定义头指针;
//LinkTail 用来定义尾指针;
2.单循环链表的初始化(带头结点,头尾指针的单循环链表)
注意:要改变主函数中头尾指针的指向,故传入二级指针
InitLinkList(LinkHead* L, LinkTail* T)
2.算法步骤:
(1) 生成新结点作头结点 并使 (头指针L)指向头结点
(2) 初始化头结点指针域指向自己
(3) 初始化尾指针指向头结点
注意与单链表初始化的区别
//2.单循环链表的初始化(仍然是初始化头结点)
//注意初始化尾指针的指向
bool InitLinkList(LinkHead* L, LinkTail* T)
{
//[1]创建头结点
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->data = 0;//头结点数据域可以自行赋值,一般不进行赋值
//以下操作与单链表初始化有所不同:
//[2]初始化头结点指针域指向自己
(*L)->next = (*L);
//[3]初始化尾指针指向头结点
(*T) = (*L);
return true;
}
3. 判断单循环链表链表是否为空
方法1: 判断头结点的next域是否指向自己
LinkListEmpty1(LinkHead L)
方法2: 判断尾指针是否指向头结点
LinkListEmpty2( LinkTail T)
//[方法1:判断头结点的next域是否指向自己]
bool LinkListEmpty1(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
//若头结点不存在,也认为该表为空表
if (L == NULL)
{
return true;
}
//[1]判断头结点的next域是否指向自己
//若头结点的next域指向自己,则说明单循环链表为空,返回true
if (L->next == L)
{
return true;
}
//否则说明单循环链表不为空,返回false
else
{
return false;
}
}
//[方法2:判断尾指针是否指向头结点]
bool LinkListEmpty2( LinkTail T)
{
//[0]判断单循环链表头结点是否存在
//若头结点不存在,也认为该表为空表
if (T == NULL)
{
return true;
}
//[1]判断尾指针是否指向头结点
//若尾指针指向头结点(也就是它本身),则说明单循环链表为空,返回true
if (T == T->next)
{
return true;
}
//否则单循环链表不为空,返回false
else
{
return false;
}
}
4.单循环链表的销毁
注意:在销毁完所有结点(包括头结点时),要重置头尾指针指向NULL,故传入二级指针
DestroyLinkList(LinkHead* L,LinkTail *T)
1.算法思路:从头结点开始,依次释放所有结点
3.算法步骤:
(1)定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
(2)初始化p指向首元结点
(3)依次销毁除头结点外的所有结点
(4)清空完单循环链表后释放头结点
(5)更新头指针和尾指针,避免悬挂指针问题
//4.单循环链表的销毁
bool DestroyLinkList(LinkHead* L,LinkTail *T)
{
//[0]判断单循环链表头结点是否存在
//一个带有头尾指针的单循环链表是由头指针和尾指针组成的,所以要判断头指针和尾指针是否为空
if (*L == NULL|| *T == NULL)
{
return false;
}
//以下操作与单链表清空操作相似;但是要注意:
// 循环终止条件应为(p!=*L)
// 在销毁完毕后,要更新头尾指针,避免指针悬挂问题
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode* p, * q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
p = (*L)->next;
//[3]依次销毁除头结点外的所有结点
while (p != *L)
//注意这里不能写(q!=(*L)),因为在这条语句之前q并未初始化(是一个野指针)
//p !=(*L)也要区别于单链表清空操作中循环的终止条件p!=NULL
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//在清空完单循环链表后释放头结点
//[4]释放头结点
free(*L);
//[5]更新头指针和尾指针,避免悬挂指针问题
*L = NULL;
*T = NULL;
return true;
}
5.单循环链表的清空
注意:
清空后要更新尾指针指向头结点,故传入尾指针的二级指针
清空后要更新头结点的next域指向头结点,不改变头指针的指向,故传入头指针
ClearLinkList(LinkHead L,LinkTail* T)
1.算法思路:依次释放所有结点,并将头结点指针域重置指向自己,更新尾指针的指向
2.算法步骤:
(1)定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q: 存储下一个待销毁结点的地址
(2)初始化p指向首元结点
(3)依次销毁除头结点外的所有结点
(4)重新初始化头尾指针(这一步是与单循环链表的销毁操作唯一的不同)
//5.单循环链表的清空
//实际上就是销毁除了头结点的其余结点
bool ClearLinkList(LinkHead L,LinkTail* T)
{
//[0]判断单循环链表头结点是否存在
//一个带有头尾指针的单循环链表是由头指针和尾指针组成的,所以要判断头指针和尾指针是否为空
if (L == NULL||(*T) == NULL)
{
return false;
}
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode* p, * q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
p = L->next;
//[3]依次销毁除头结点外的所有结点
while (p != L)
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//循环销毁完毕后: 当前 p 指向头结点,因此不需要释放头结点,因为头结点应该保留在链表中
//[4]重新初始化头尾指针
L->next = L; // 确保头结点的 next 指针指向自身,表示链表为空
(*T) = L;// 确保尾指针指向头结点,表示链表为空
return true;
}
6.求单循环链表的表长
可以从任意结点开始遍历,只要设置好正确的终止条件即可,这里使用头指针遍历
LinkListLength(LinkHead L)
1.算法步骤:
(1)定义一个临时指针p,并让其指向首元结点
(2)初始化计数器为0(首元结点不存在时,没有进行循环操作,直接返回0)
(3)循环遍历,统计结点数
//6.求单循环链表的表长
//返回L中的元素个数
//可以从任意结点开始遍历,这里使用头指针遍历,与单链表操作相似(但要注意判空操作:p!=L;)
int LinkListLength(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1]定义一个临时指针p,并让其指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
LNode* p = L->next;
//[2]初始化计数器为0,当首元结点不存在时,没有进行循环操作,直接返回0
int i = 0;
//[3]循环遍历,统计结点数
while (p != L )
{
i++;//进入循环,证明存在首元结点,故先叠加
p = p->next;//当p指向空,计数器已经将表长记录成功
}
return i;
}
7.单循环链表的按位查找
方法一:用头指针遍历
GetElemLinkList_H(LinkHead L, int i, ElemType* e)
方法二:用尾指针遍历
GetElemLinkList_T(LinkTail T, int i, ElemType* e)
1.头指针遍历------算法步骤:
(1)初始化 p 指向首元结点
(2)初始化计数器为 1
(3)循环查找第i个元素
(4)异常判断
// p == L
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
(5)获取第 i 个结点的值
//7.单循环链表的按位查找
//取单循链表中第i个元素的内容
// [方法1:从头指针开始遍历]与单链表的查找操作一致
bool GetElemLinkList_H(LinkHead L, int i, ElemType* e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2] 初始化计数器为 1
int j = 1;
//[3] 查找
// p != L:
// 1. 保证首元结点不存在时,不进入循环
// 2. 保证 i 大于链表长度时,在 p 指向头结点时终止循环
// j < i:
//正常情况下: 保证循环终止时 p 指向第 i 个结点
while (p != L && j < i)
{
p = p->next; // 移动到下一个结点
j++; // 计数器叠加
}
//[4] 异常判断
// p == L
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
if (p == L || j > i)
{
return false;
}
//[5] 获取第 i 个结点的值
*e = p->data;
return true;
}
2.尾指针遍历------算法步骤:
(1)初始化 p 指向首元结点 :T->next->next
(2)初始化计数器为 1
(3)循环查找第i个元素
(4)异常判断
// p == T->next
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
(5)获取第 i 个结点的值
// [方法2:从尾指针开始遍历]
//单循环链表只有尾指针时,只能使用尾指针
bool GetElemLinkList_T(LinkTail T, int i, ElemType* e)
{
//[0]判断单循环链表头结点是否存在
if (T == NULL)
{
return false;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),T->next->next会使p指向头结点T(T->next),不会进入循环
LNode* p = T->next->next;
//[2] 初始化计数器为 1
int j = 1;
// [3] 查找
// p != T->next:
// 1. 保证首元结点不存在时,不进入循环
// 2. 保证 i 大于链表长度时,在 p 指向头结点时终止循环
// j < i:
//正常情况下: 保证循环终止时 p 指向第 i 个结点
while (p != T->next && j < i)
{
p = p->next;// 移动到下一个结点
j++;//计数器叠加
}
// [4] 异常判断
// p == T->next
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
if (p == T->next || j > i)
{
return false;
}
// [5] 获取第 i个结点的值
*e = p->data;
return true;
}
8.单循环链表的按值查找(同样可以用头尾指针分别遍历,这里采用头指针)
方法1:返回地址
LNode* LocateLinkList1(LinkHead L, ElemType e)
方法1:返回位序
int LocateLinkList2(LinkHead L, ElemType e)
1.返回地址------算法步骤:
(1)初始化 p 指向首元结点
(2)遍历链表,并寻找与元素e相同的结点
(3)循环完毕,未找到对应元素时,返回NULL
(4)异常判断:
//p == L:单链表在这里是可以直接返回p的,但单循环链表不能这样返回,因为p指向的是头结点
(4)查找成功,返回对应地址
// 8.1返回地址(与单链表类似)
LNode* LocateLinkList1(LinkHead L, ElemType e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return NULL;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2]遍历链表,并寻找与元素e相同的结点
//p!=L:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时(实际上现在指向头结点)终止循环
//p->data!=e:
// 保证循环终止时p指向与元素e相同的结点
while (p != L && p->data != e)
{
p = p->next;
}
//[3]循环完毕,未找到对应元素时,返回NULL
//单链表在这里是可以直接返回p的,但单循环链表不能这样返回,因为p指向的是头结点
if (p == L)
{
printf("表中无对应元素!\n");
return NULL;
}
//[4]查找成功,返回对应地址
return p;
}
2.返回位序------算法步骤:
(1)初始化 p 指向首元结点
(2)初始化计数器为1
(3)遍历链表,并寻找与元素e相同的结点
(4)异常判断
//p == L:
//1.首元结点不存在的情况
//2.链表中无与元素e相同结点的情况
(5)查找成功,返回位序
// 8.2返回位序(与单链表类似)
int LocateLinkList2(LinkHead L, ElemType e)
{
//[0] 判断单循环链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int count = 1;
//[3]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next(实际上现在指向头结点)时终止循环
//p->data != e:
// 保证循环终止时p指向第与元素e相同的结点
while (p != L && p->data != e)
{
p = p->next;
count++;
}
//[4]异常判断
//p == L:
//1.首元结点不存在的情况
//2.链表中无与元素e相同结点的情况
if (p == L)
{
return 0;//返回0表示查找失败
}
//[5]查找成功,返回位序
return count;
}
9.单循环链表的按位插入
注意:若插入位置为单循环链表表尾,则需要更新尾指针的指向,故传入尾指针的二级指针
InsertLinkList(LinkHead L, LinkTail *T,int i, ElemType e)
1.算法步骤:
(1)初始化临时指针p指向头结点
(2) 初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
(3)循环寻找第i-1个结点
注意这里终止条件
//p->next!=L:
// 1.保证查找一周后,在p指向尾结点时结束循环
// 2.保证传入的i 大于等于 表长加1时,循环到p指向尾结点时终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
(4)异常判断
//j!=i:
//1.i<1的情况:不进入循环,最终j==0,不等于i-1
//2.i>表长加1的情况:进入循环,最终p指向尾结点,j等于表长
(5)为新结点动态分配内存
(6)初始化新结点数据域
(7)将新结点插入到第i-1个结点之后
(8)如果在表尾插入元素:需要改变尾指针的指向,指向新的尾结点
//9.单循环链表的按位插入
bool InsertLinkList(LinkHead L, LinkTail *T,int i, ElemType e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点
LNode* p = L;
//[2]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[3]循环寻找第i-1个结点
//p->next!=L:
// 1.保证查找一周后,在p指向尾结点时结束循环
// 2.保证传入的i 大于等于 表长加1时,循环到p指向尾结点时终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != L && j < i - 1)
{
p = p->next;
j++;
}
//[4]异常判断
//1.i<1的情况:不进入循环,最终j==0,不等于i-1
//2.i>表长加1的情况:进入循环,最终p指向尾结点,j等于表长
//例:表长为5,i=7, j=5 j!=i-1即5!=6
if (j != i - 1)
{
return false;
}
//[5]为新结点动态分配内存
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL)
{
printf("内存分配失败!\n");
return false;
}
//[6]初始化新结点数据域
s->data = e;
//[7]将新结点插入到第i-1个结点之后(这里顺序不能互换,互换后会断链)
//这里包含了在表头和表尾插入的情况
s->next = p->next;
p->next = s;
//注意:在改变完指针的指向后,判断是否在表尾进行操作
//如果在表尾插入元素:需要改变尾指针的指向,指向新的尾结点
if (p==*T)
{
*T = s;
}
return true;
}
10.单循环链表的按位删除
注意:若删除位置为单循环链表表尾,则需要更新尾指针指向待删除位置的前一个元素,故传入尾指针的二级指针
DeleteLinkList(LinkHead L, LinkTail* T, int i,ElemType *e)
1.算法步骤:
(1)初始化临时指针p指向头结点(p用来寻找第i-1个结点)
(2)定义临时指针q(q用来销毁待删除结点)
(3)初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
(4)循环寻找第i-1个结点
//p->next!=L:
// 1.保证首元结点不存在时,不进入循环
// 2.保证传入的i超过表长时,循环到p指向尾结点时(p->next为L)终止循环
//j<i-1
// 保证循环终止时p指向第i-1个结点
(5)异常判断
//p->next == L:
//1.首元结点不存在的情况
//2.i大于链表长度的情况(i==链表长度+1:p指向尾结点,p->next指向L),无法删除不存在的结点
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
(6)用q指向待删除结点并记录数据
(7)若在表尾删除结点:需要改变尾指针的指向,指向新的尾结点
(8)删除结点(注意不要断链)
bool DeleteLinkList(LinkHead L, LinkTail* T, int i,ElemType *e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点(p用来寻找第i-1个结点)
LNode* p = L;
//[2]定义临时指针q(q用来销毁待删除结点)
LNode* q;
//[3]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[4]循环寻找第i-1个结点
//p->next!=L:
// 1.保证首元结点不存在时,不进入循环
// 2.保证传入的i超过表长时,循环到p指向尾结点时(p->next为L)终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != L && j < i - 1)
{
p = p->next;// 移动到下一个结点
j++;//计数器加一
}
//[5]异常判断
//p->next == L:
//1.首元结点不存在的情况
//2.i大于链表长度的情况(i==链表长度+1:p指向尾结点,p->next指向L),无法删除不存在的结点
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p->next==L||j > i - 1)
{
return false;
}
//[6]用q指向待删除结点并记录数据
q = p->next;
*e = q->data;
//[7]若在表尾删除结点:需要改变尾指针的指向,指向新的尾结点
if (q == *T)
{
*T = p;
}
//[8]删除结点
p->next = q->next;
free(q);
return true;
}
11.头插法建立单循环链表
注意:头尾指针的更新,传入二级指针
CreateLinkList_H(LinkHead* L, LinkTail* T, int n)
1.算法步骤:
(1)建立一个空的头结点并初始化,且初始化尾指针
(2)循环建立新结点并插入到表头
[[1]]建立新结点并初始化data域
[[2]]将新结点插入到表头
[[3]]注意头插法建立单循环链表时,只需要更新一次尾指针 指向第一次插入的新结点
(3)插入完成后确保尾结点的 next 指向头结点
//11.头插法建立单循环链表:
bool CreateLinkList_H(LinkHead* L, LinkTail* T, int n)
{
//[1]建立一个空的头结点并初始化,且初始化尾指针
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = *L;//初始化头结点next域指向自己
*T = *L;//初始化尾指针指向头结点
//[2]循环建立新结点并插入到表头
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &p->data);
//[[2]]将新结点插入到表头
p->next = (*L)->next;//新结点的next域应指向原来的首元结点
(*L)->next = p;//头结点的next域指向新结点
//[[3]]注意头插法建立单循环链表时,只需要更新一次尾指针 指向第一次插入的新结点
if (i == 0)
{
*T = p;
}
}
//[3]插入完成后确保尾结点的 next 指向头结点(实际上在循环中已经实现过了,可以省略)
(*T)->next = *L;
return true;
}
12.尾插法建立单循环链表
CreateLinkList_T(LinkHead* L, LinkTail* T, int n)
1.算法步骤:
(1)建立一个空的头结点并初始化,且初始化尾指针
(2)循环建立新结点并插入到表尾
[[1]]建立新结点并初始化data域
[[2]]将新结点插入到表尾
[[3]]注意尾插法建立单循环链表时,每插入一个新结点尾指针 都要更新指向新插入的结点
(3)插入完成后确保尾结点的 next 指向头结点
bool CreateLinkList_T(LinkHead* L, LinkTail* T, int n)
{
//[1]建立一个空的头结点并初始化,且初始化尾指针
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = (*L);//初始化头结点next域指向自己
*T = *L;//初始化尾指针指向头结点
//[2]循环建立新结点并插入到表尾
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &p->data);
//[[2]]将新结点插入到表尾
p->next = *L;//新结点的next域应指向头结点
(*T)->next = p;//原来的尾结点的next域指向新结点
*T = p;//更新尾指针指向新的尾结点
}
//[3]插入完成后确保尾结点的 next 指向头结点(实际上在循环中已经实现过了,可以省略)
(*T)->next = *L;
return true;
}
13.输出单循环链表的所有元素
方法1:头指针遍历输出
printLinkList_H(LinkHead L)
方法2:尾指针遍历输出
printLinkList_T(LinkTail T)
1.头指针遍历输出------算法步骤:
(1)初始化 p 指向头结点
(2)判断链表是否为空(即只有头结点)
(3)遍历链表并输出所有结点元素,直到最后一个结点
//13.1头指针法
bool printLinkList_H(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化 p 指向头结点
LNode* p = L->next;
//[2]判断链表是否为空(即只有头结点)
if (p == L)
{
printf("NULL LinkList!\n");// 输出 NULL LinkList! 代表链表为空
return false;// 直接返回,不继续执行后续代码
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p!=L)
{
printf("%d-->", p->data); // 输出当前结点的数据
p = p->next;// 移动指针到下一个结点
}
printf("end\n");
return true;
}
1.尾指针遍历输出------算法步骤:
(1)初始化 p 指向头结点 T->next->next
(2)判断链表是否为空(即只有头结点) p == T->next
(3)遍历链表并输出所有结点元素,直到最后一个结点
//13.2尾指针法
bool printLinkList_T(LinkTail T)
{
//[0]判断单循环链表头结点是否存在
if (T == NULL||T->next==NULL)
{
return false;
}
//[1]初始化 p 指向头结点
LNode* p = T->next->next;
//[2]判断链表是否为空(即只有头结点)
if (p == T->next)
{
printf("NULL List!\n");
return false;
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p != T->next)
{
printf("%d-->", p->data);// 输出当前结点的数据
p = p->next;// 移动指针到下一个结点
}
printf("end\n");
return true;
}
14.带尾指针的循环链表的合并
注意:合并后返回整表的尾指针
LNode * ConnectLinkList(LinkTail Ta, LinkTail Tb)
1.算法步骤:
(1)用p保存Ta的头结点
(2)将Tb的表头接在Ta的表尾
(3)释放Tb的头结点
(4)修改Tb的尾结点next域指向Ta的头结点
(5)返回合并后的链表
2.图解:
//14.带尾指针的循环链表的合并
//假设Ta和Tb是两个非空的单循环链表,要求合并Ta和Tb,合并后的链表依然为循环链表
LNode * ConnectLinkList(LinkTail Ta, LinkTail Tb)
{
//[1]用p保存Ta的头结点
LNode* p = Ta->next;
//[2]将Tb的表头接在Ta的表尾
Ta->next = Tb->next->next;
//[3]释放Tb的头结点
free(Tb->next);
//[4]修改Tb的尾结点next域指向Ta的头结点
Tb->next = p;
return Tb;//Tb是合并后的链表(尾指针)
}
15.所有操作代码如下:
//单循环链表的实现(带头结点,带头尾指针)
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
#define bool int
#define false 0
#define true 1
//1.单循环链表的结构定义不变
typedef struct LNode
{
ElemType data;//数据域
struct LNode* next;//指针域
}LNode, * LinkHead,* LinkTail;
//LNode *用来定义结点指针;
//LinkHead 用来定义头指针;
//LinkTail 用来定义尾指针;
//2.单循环链表的初始化(仍然是初始化头结点)
//注意初始化尾指针的指向
bool InitLinkList(LinkHead* L, LinkTail* T)
{
//[1]创建头结点
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->data = 0;//头结点数据域可以自行赋值,一般不进行赋值
//以下操作与单链表初始化有所不同:
//[2]初始化头结点指针域指向自己
(*L)->next = (*L);
//[3]初始化尾指针指向头结点
(*T) = (*L);
return true;
}
//3.判断单循环链表是否为空
//[方法1:判断头结点的next域是否指向自己]
bool LinkListEmpty1(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]判断头结点的next域是否指向自己
//若头结点的next域指向自己,则说明单循环链表为空,返回true
if (L->next == L)
{
return true;
}
//否则说明单循环链表不为空,返回false
else
{
return false;
}
}
//[方法2:判断尾指针是否指向头结点]
bool LinkListEmpty2( LinkTail T)
{
//[0]判断单循环链表头结点是否存在
if (T == NULL)
{
return false;
}
//[1]判断尾指针是否指向头结点
//若尾指针指向头结点(也就是它本身),则说明单循环链表为空,返回true
if (T == T->next)
{
return true;
}
//否则单循环链表不为空,返回false
else
{
return false;
}
}
//4.单循环链表的销毁
bool DestroyLinkList(LinkHead* L,LinkTail *T)
{
//[0]判断单循环链表头结点是否存在
//一个带有头尾指针的单循环链表是由头指针和尾指针组成的,所以要判断头指针和尾指针是否为空
if (*L == NULL|| *T == NULL)
{
return false;
}
//以下操作与单链表清空操作相似;但是要注意:
// 循环终止条件应为(p!=*L)
// 在销毁完毕后,要更新头尾指针,避免指针悬挂问题
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode* p, * q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
p = (*L)->next;
//[3]依次销毁除头结点外的所有结点
while (p != *L)
//注意这里不能写(q!=(*L)),因为在这条语句之前q并未初始化(是一个野指针)
//p !=(*L)也要区别于单链表清空操作中循环的终止条件p!=NULL
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//在清空完单循环链表后释放头结点
//[4]释放头结点
free(*L);
//[5]更新头指针和尾指针,避免悬挂指针问题
*L = NULL;
*T = NULL;
return true;
}
//5.单循环链表的清空
//实际上就是销毁除了头结点的其余结点
bool ClearLinkList(LinkHead L,LinkTail* T)
{
//[0]判断单循环链表头结点是否存在
//一个带有头尾指针的单循环链表是由头指针和尾指针组成的,所以要判断头指针和尾指针是否为空
if (L == NULL||(*T) == NULL)
{
return false;
}
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode* p, * q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
p = L->next;
//[3]依次销毁除头结点外的所有结点
while (p != L)
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//循环销毁完毕后: 当前 p 指向头结点,因此不需要释放头结点,因为头结点应该保留在链表中
//[4]重新初始化头尾指针
L->next = L; // 确保头结点的 next 指针指向自身,表示链表为空
(*T) = L;// 确保尾指针指向头结点,表示链表为空
return true;
}
//6.求单循环链表的表长
//返回L中的元素个数
//可以从任意结点开始遍历,这里使用头指针遍历,与单链表操作相似(但要注意判空操作:p!=L;)
int LinkListLength(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1]定义一个临时指针p,并让其指向首元结点
//若首元结点不存在(空表),(*L)->next会使p指向头结点(*L),不会进入循环
LNode* p = L->next;
//[2]初始化计数器为0,当首元结点不存在时,没有进行循环操作,直接返回0
int i = 0;
//[3]循环遍历,统计结点数
while (p != L )
{
i++;//进入循环,证明存在首元结点,故先叠加
p = p->next;//当p指向空,计数器已经将表长记录成功
}
return i;
}
//7.单循环链表的按位查找
//取单循链表中第i个元素的内容
// [方法1:从头指针开始遍历]与单链表的查找操作一致
bool GetElemLinkList_H(LinkHead L, int i, ElemType* e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2] 初始化计数器为 1
int j = 1;
//[3] 查找
// p != L:
// 1. 保证首元结点不存在时,不进入循环
// 2. 保证 i 大于链表长度时,在 p 指向头结点时终止循环
// j < i:
//正常情况下: 保证循环终止时 p 指向第 i 个结点
while (p != L && j < i)
{
p = p->next; // 移动到下一个结点
j++; // 计数器叠加
}
//[4] 异常判断
// p == L
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
if (p == L || j > i)
{
return false;
}
//[5] 获取第 i 个结点的值
*e = p->data;
return true;
}
// [方法2:从尾指针开始遍历]
//单循环链表只有尾指针时,只能使用尾指针
bool GetElemLinkList_T(LinkTail T, int i, ElemType* e)
{
//[0]判断单循环链表头结点是否存在
if (T == NULL)
{
return false;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),T->next->next会使p指向头结点T(T->next),不会进入循环
LNode* p = T->next->next;
//[2] 初始化计数器为 1
int j = 1;
// [3] 查找
// p != T->next:
// 1. 保证首元结点不存在时,不进入循环
// 2. 保证 i 大于链表长度时,在 p 指向头结点时终止循环
// j < i:
//正常情况下: 保证循环终止时 p 指向第 i 个结点
while (p != T->next && j < i)
{
p = p->next;// 移动到下一个结点
j++;//计数器叠加
}
// [4] 异常判断
// p == T->next
//1.表示首元结点不存在时,异常退出
//2.表示 i > 链表长度时,异常退出
// j < i
// 表示 i < 1时,异常退出
if (p == T->next || j > i)
{
return false;
}
// [5] 获取第 i个结点的值
*e = p->data;
return true;
}
//8.单循环链表的按值查找(同样可以用头尾指针分别遍历,这里采用头指针)
// 8.1返回地址(与单链表类似)
LNode* LocateLinkList1(LinkHead L, ElemType e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return NULL;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2]//[2]遍历链表,并寻找与元素e相同的结点
//p!=L:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时(实际上现在指向头结点)终止循环
//p->data!=e:
// 保证循环终止时p指向与元素e相同的结点
while (p != L && p->data != e)
{
p = p->next;
}
//[3]循环完毕,未找到对应元素时,返回NULL
//单链表在这里是可以直接返回p的,但单循环链表不能这样返回,因为p指向的是头结点
if (p == L)
{
printf("表中无对应元素!\n");
return NULL;
}
//[4]查找成功,返回对应地址
return p;
}
// 8.2返回位序(与单链表类似)
int LocateLinkList2(LinkHead L, ElemType e)
{
//[0] 判断单循环链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向头结点L,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int count = 1;
//[3]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next(实际上现在指向头结点)时终止循环
//p->data != e:
// 保证循环终止时p指向第与元素e相同的结点
while (p != L && p->data != e)
{
p = p->next;
count++;
}
//[4]异常判断
//p == L:
//1.首元结点不存在的情况
//2.链表中无与元素e相同结点的情况
if (p == L)
{
return 0;//返回0表示查找失败
}
//[5]查找成功,返回位序
return count;
}
//9.单循环链表的按位插入
bool InsertLinkList(LinkHead L, LinkTail *T,int i, ElemType e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点
LNode* p = L;
//[2]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[3]循环寻找第i-1个结点
//p->next!=L:
// 1.保证查找一周后,在p指向尾结点时结束循环
// 2.保证传入的i 大于等于 表长加1时,循环到p指向尾结点时终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != L && j < i - 1)
{
p = p->next;
j++;
}
//[4]异常判断
//1.i<1的情况:不进入循环,最终j==0,不等于i-1
//2.i>表长加1的情况:进入循环,最终p指向尾结点,j等于表长
//例:表长为5,i=7, j=5 j!=i-1即5!=6
if (j != i - 1)
{
return false;
}
//[5]为新结点动态分配内存
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL)
{
printf("内存分配失败!\n");
return false;
}
//[6]初始化新结点数据域
s->data = e;
//[7]将新结点插入到第i-1个结点之后(这里顺序不能互换,互换后会断链)
//这里包含了在表头和表尾插入的情况
s->next = p->next;
p->next = s;
//注意:在改变完指针的指向后,判断是否在表尾进行操作
//如果在表尾插入元素:需要改变尾指针的指向,指向新的尾结点
if (p==*T)
{
*T = s;
}
return true;
}
//10.单循环链表的按位删除
bool DeleteLinkList(LinkHead L, LinkTail* T, int i,ElemType *e)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点(p用来寻找第i-1个结点)
LNode* p = L;
//[2]定义临时指针q(q用来销毁待删除结点)
LNode* q;
//[3]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[4]循环寻找第i-1个结点
//p->next!=L:
// 1.保证首元结点不存在时,不进入循环
// 2.保证传入的i超过表长时,循环到p指向尾结点时(p->next为L)终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != L && j < i - 1)
{
p = p->next;// 移动到下一个结点
j++;//计数器加一
}
//[5]异常判断
//p->next == L:
//1.首元结点不存在的情况
//2.i大于链表长度的情况(i==链表长度+1:p指向尾结点,p->next指向L),无法删除不存在的结点
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p->next==L||j > i - 1)
{
return false;
}
//[6]用q指向待删除结点并记录数据
q = p->next;
*e = q->data;
//[7]若在表尾删除结点:需要改变尾指针的指向,指向新的尾结点
if (q == *T)
{
*T = p;
}
//[8]删除结点
p->next = q->next;
free(q);
return true;
}
//11.头插法建立单循环链表:
bool CreateLinkList_H(LinkHead* L, LinkTail* T, int n)
{
//[1]建立一个空的头结点并初始化,且初始化尾指针
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = *L;//初始化头结点next域指向自己
*T = *L;//初始化尾指针指向头结点
//[2]循环建立新结点并插入到表头
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &p->data);
//[[2]]将新结点插入到表头
p->next = (*L)->next;//新结点的next域应指向原来的首元结点
(*L)->next = p;//头结点的next域指向新结点
//[[3]]注意头插法建立单循环链表时,只需要更新一次尾指针 指向第一次插入的新结点
if (i == 0)
{
*T = p;
}
}
//[3]插入完成后确保尾结点的 next 指向头结点(实际上在循环中已经实现过了,可以省略)
(*T)->next = *L;
return true;
}
//12.尾插法建立单循环链表
bool CreateLinkList_T(LinkHead* L, LinkTail* T, int n)
{
//[1]建立一个空的头结点并初始化,且初始化尾指针
*L = (LinkHead)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = (*L);//初始化头结点next域指向自己
*T = *L;//初始化尾指针指向头结点
//[2]循环建立新结点并插入到表尾
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &p->data);
//[[2]]将新结点插入到表尾
p->next = *L;//新结点的next域应指向头结点
(*T)->next = p;//原来的尾结点的next域指向新结点
*T = p;//更新尾指针指向新的尾结点
}
//[3]插入完成后确保尾结点的 next 指向头结点(实际上在循环中已经实现过了,可以省略)
(*T)->next = *L;
return true;
}
//13.1输出单循环链表的所有元素
//头指针法
bool printLinkList_H(LinkHead L)
{
//[0]判断单循环链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化 p 指向头结点
LNode* p = L->next;
//[2]判断链表是否为空(即只有头结点)
if (p == L)
{
printf("NULL LinkList!\n");// 输出 NULL LinkList! 代表链表为空
return false;// 直接返回,不继续执行后续代码
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p!=L)
{
printf("%d-->", p->data); // 输出当前结点的数据
p = p->next;// 移动指针到下一个结点
}
printf("end\n");
return true;
}
//13.2尾指针法
bool printLinkList_T(LinkTail T)
{
//[0]判断单循环链表头结点是否存在
if (T == NULL||T->next==NULL)
{
return false;
}
//[1]初始化 p 指向头结点
LNode* p = T->next->next;
//[2]判断链表是否为空(即只有头结点)
if (p == T->next)
{
printf("NULL List!\n");
return false;
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p != T->next)
{
printf("%d-->", p->data);// 输出当前结点的数据
p = p->next;// 移动指针到下一个结点
}
printf("end\n");
return true;
}
//14.带尾指针的循环链表的合并
//假设Ta和Tb是两个非空的单循环链表,要求合并Ta和Tb,合并后的链表依然为循环链表
LNode * ConnectLinkList(LinkTail Ta, LinkTail Tb)
{
//[1]用p保存Ta的头结点
LNode* p = Ta->next;
//[2]将Tb的表头接在Ta的表尾
Ta->next = Tb->next->next;
//[3]释放Tb的头结点
free(Tb->next);
//[4]修改Tb的尾结点next域指向Ta的头结点
Tb->next = p;
return Tb;//Tb是合并后的链表(尾指针)
}
int main()
{
LinkHead La;//定义单循环链表Ta的头指针
LinkTail Ta;//定义单循环链表Ta的尾指针
LinkHead Lb;//定义单循环链表Tb的头指针
LinkTail Tb;//定义单循环链表Tb的尾指针
//头插法创建单循环链表Ta
printf("头插法创建单循环链表Ta:\n");
CreateLinkList_H(&La, &Ta, 5);
printLinkList_H(La);
printf("\n");
//尾插法创建单循环链表Tb
printf("尾插法创建单循环链表Tb:\n");
CreateLinkList_T(&Lb, &Tb, 6);
printLinkList_H(Lb);
printf("\n");
//求单循环链表的长度
printf("单循环链表Ta的长度为%d:\n",LinkListLength(La));
printf("单循环链表Tb的长度为%d:\n",LinkListLength(Lb));
printf("\n");
//判断单循环链表Ta是否为空
printf("判断单循环链表Ta是否为空:\n");
if (LinkListEmpty1(La))
{
printf("单循环链表Ta为空\n");
}
else
{
printf("单循环链表Ta不为空\n");
}
printf("\n");
//查找单循环链表Ta的第3个元素
printf("查找单循环链表Ta的第3个元素:\n");
ElemType e1;
GetElemLinkList_H(La, 3, &e1);
printf("单循环链表Ta的第3个元素为:%d\n", e1);
printf("\n");
//查找单循环链表Tb的第5个元素
printf("查找单循环链表Tb的第5个元素:\n");
ElemType e2;
GetElemLinkList_T(Tb, 5, &e2);
printf("单循环链表Tb的第5个元素为:%d\n", e2);
//查找与单循环链表Ta中与3相等的元素
printf("\n");
printf("查找单循环链表Ta中与3相等的元素的地址:\n");
LNode* m1=LocateLinkList1(La, 3);
printf("单循环链表Ta中与3相等的元素为:%d\n", m1->data);
//查找单循环链表Tb中与4相等的元素
printf("\n");
printf("查找单循环链表Tb中与4相等的元素的位序:\n");
int m2 = LocateLinkList2(Lb, 4);
printf("单循环链表Tb中与4相等的元素的位序为:%d\n", m2);
//在单循环链表Tb中插入一些元素
printf("\n");
printf("在单循环链表Tb中插入一些元素\n");
printf("第1位插入12:\n");
InsertLinkList(Lb, &Tb, 1, 12);
printLinkList_H(Lb);
printf("第8位插入12:\n");
InsertLinkList(Lb, &Tb, 8, 12);
printLinkList_H(Lb);
printf("第4位插入13:\n");
InsertLinkList(Lb, &Tb, 4, 13);
printf("插入后的单循环链表Tb为:\n");
printLinkList_H(Lb);
//在单循环链表Tb中删除一些元素
printf("\n");
printf("在单循环链表Tb中删除一些元素\n");
printf("删除第1位:\n");
ElemType r1;
DeleteLinkList(Lb, &Tb, 1, &r1);
printf("删除的元素值为:%d\n", r1);
printLinkList_H(Lb);
printf("删除第3位:\n");
ElemType r2;
DeleteLinkList(Lb, &Tb, 3, &r2);
printf("删除的元素值为:%d\n", r2);
printLinkList_H(Lb);
printf("删除第2位:\n");
ElemType r3;
DeleteLinkList(Lb, &Tb, 2, &r3);
printf("删除的元素值为:%d\n", r3);
printLinkList_H(Lb);
printf("删除后的单循环链表Tb为:\n");
printLinkList_H(Lb);
printf("\n");
//销毁单循环链表Ta
printf("销毁单循环链表Ta:\n");
if (DestroyLinkList(&La, &Ta))
{
printf("销毁单循环链表Ta成功!\n");
}
else
{
printf("销毁单循环链表Ta失败!\n");
}
printf("\n");
//清空单循环链表Tb
printf("清空单循环链表Tb:\n");
if (ClearLinkList(Lb, &Tb))
{
printf("清空单循环链表Tb成功!\n");
}
else
{
printf("清空单循环链表Tb失败!\n");
}
printf("\n");
LinkHead Lc;//定义单循环链表Tc的头指针
LinkTail Tc;//定义单循环链表Tc的尾指针
LinkHead Ld;//定义单循环链表Td的头指针
LinkTail Td;//定义单循环链表Td的尾指针
//头插法创建单循环链表Tc
printf("头插法创建单循环链表Tc:\n");
CreateLinkList_H(&Lc, &Tc, 3);
printLinkList_H(Lc);
printf("\n");
//尾插法创建单循环链表Td
printf("尾插法创建单循环链表Td:\n");
CreateLinkList_T(&Ld, &Td, 4);
printLinkList_H(Ld);
printf("\n");
//连接两个单循环链表Tc和Td
LinkTail Tb1=ConnectLinkList(Tc, Td);
printf("\n");
printf("合并后的单循环链表Tb1:\n");
printLinkList_T(Tb1);
printf("\n");
//判断单循环链表Tb1是否为空
printf("判断单循环链表Tb1是否为空:\n");
if (LinkListEmpty2(Tb1))
{
printf("单循环链表Tb1为空\n");
}
else
{
printf("单循环链表Tb1不为空\n");
}
return 0;
}