07. 循环链表
一、什么是循环链表
循环链表是一种头尾相接的链表,即表中最后一个节点的指针域指向第一个节点,整个链表形成一个环。我们从表中任一节点出发均可找到其它节点。
typedef int ElementType;
typedef struct LNode {
ElementType Data;
struct LNode * Next;
} Lnode, * List;
二、求表长
/**
* @brief 求循环链表的表长
*
* @param PtrL 循环链表
* @return int 循环链表的长度
*/
int Length(List PtrL)
{
List p = PtrL;
int j = 1;
if (PtrL == NULL)
{
return 0;
}
while (p->Next != PtrL)
{
p = p->Next;
j++; // 当前p指向的是第j个节点
}
return j;
}
三、查找元素
/**
* @brief 按序号查找
*
* @param PtrL 循环链表
* @param K 要查找的序号
* @return List 如果找到返回指向第K个的指针,否则返回NULL
*/
List FindKth(List PtrL, int K)
{
List p = PtrL; // p指向表的第一个节点
int i = 1;
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (i < 1 || i > Length(PtrL) + 1)
{
printf("查找的位置不合法\n");
return PtrL;
}
while (p->Next != PtrL && i < K)
{
p = p->Next;
i++; // 当前p指向的是第j个节点
}
return (i == K) ? p : NULL;
}
/**
* @brief 按值查找
*
* @param PtrL 循环链表
* @param X 要查找的元素
* @return List 如果找到返回指向X的指针,否则返回NULL
*/
List FindElement(List PtrL, ElementType X)
{
List p = PtrL; // p指向表的第一个节点
if (PtrL == NULL)
{
printf("链表为空!\n");
return NULL;
}
while (p->Data != X)
{
if (p->Next == PtrL)
{
return NULL;
}
p = p->Next;
}
return p;
}
四、插入元素
- 先构造一个新节点,用 s 执行。
- 再找到链表的第 i-1 个结点,用 p 指向。
- 然后修改指针,插入结点(p 之后插入新结点是 s)。
/**
* @brief 插入一个元素
*
* @param PtrL 循环链表
* @param i 要插入的位置
* @param X 要插入的元素
*
* @return List 指向添加元素的循环链表的头指针
*/
List Insert(List PtrL, int i, ElementType X)
{
List p = NULL, s = NULL;
if (i < 1 || i > Length(PtrL) + 1)
{
printf("插入的位置不合法\n");
return PtrL;
}
if (PtrL == NULL)
{
PtrL = (List)malloc(sizeof(LNode));
PtrL->Data = X;
PtrL->Next = PtrL;
return PtrL;
}
if (i == 1) // 新节点插入在表头
{
s = (List)malloc(sizeof(LNode)); // 申请新节点
s->Data = X; // 新节点的数据域为表头
s->Next = PtrL; // 新节点指向第二个节点
p = FindKth(PtrL, Length(PtrL)); // 找到最后一个节点
p->Next = s; // 最后一个节点指向新节点
return s;
}
p = FindKth(PtrL, i - 1); // 找到第i-1个节点
if (p == NULL) // 插入位置不合法
{
printf("插入的位置不合法\n");
return PtrL;
}
s = (List)malloc(sizeof(LNode)); // 申请新节点
s->Data = X;
s->Next = p->Next; // 新节点指向p的下一个节点
p->Next = s; // p指向新节点
return PtrL;
}
五、删除元素
- 先找到链表的第 i 个节点,用 p 指向。
- 然后修改指针,删除 p 所指节点。
- 最后释放 p 所指节点的空间。
/**
* @brief 删除一个元素
*
* @param PtrL 链表
* @param i 要删除元素的位置
* @return List 指向删除后的循环链表的头指针
*/
List DeleteByPositon(List PtrL, int i)
{
List p = NULL, s = NULL;
if (i < 1 || i > Length(PtrL))
{
printf("删除的位置不合法\n");
return PtrL;
}
if (i == 1) // 要删除的节点是表头
{
p = FindKth(PtrL, Length(PtrL)); // 找到表尾
s = PtrL; // s指向表头
PtrL = PtrL->Next; // 删除表头,表头指向下一个节点
p->Next = PtrL; // 表尾重新指向表头
free(s); // 释放s
return PtrL;
}
p = FindKth(PtrL, i - 1); // 找到第i-1个节点
if (p == NULL)
{
printf("第%d个节点不存在!\n", i - 1);
}
else if (p->Next == PtrL)
{
printf("第%d个节点不存在!\n", i);
}
else
{
s = p->Next; // s指向第i个节点
p->Next = s->Next; // p指向第i+1个节点
free(s); // 释放s
}
return PtrL;
}
/**
* @brief 按元素删除
*
* @param PtrL 链表
* @param X 要删除的元素
* @return List 指向删除后的链表的头指针
*/
List DeleteByElement(List PtrL, ElementType X)
{
List p = PtrL;
LNode * node = FindElement(PtrL, X);
LNode * next_node = NULL;
if (p == NULL)
{
printf("链表为空!\n");
return NULL;
}
if (node == NULL)
{
printf("要删除的元素不存在!\n");
return PtrL;
}
next_node = node->Next;
if (next_node != PtrL)
{
node->Data = next_node->Data;
node->Next = next_node->Next;
free(next_node);
return PtrL;
}
else
{
return DeleteByPositon(PtrL, Length(PtrL));
}
}
六、遍历链表
由于循环链表中没有 NULL 指针,故遍历操作时,其终止条件就不像非循环链表那样判断 p 或 p->next 是否为空,而是判断它们是否头指针。
/**
* @brief 遍历链表
*
* @param PtrL 链表
*/
void PrintCircularLinkedList(List PtrL)
{
List p = PtrL;
if (p == NULL)
{
return;
}
while (1)
{
printf("%d ", p->Data);
if (p->Next == PtrL)
{
break;
}
p = p->Next;
}
printf("\n");
}