一、 基础知识
循环链表顾名思义是一个循环的单链表,就像小时候玩的丢手绢一样,是一个圈,首尾相连,形成一个类似环状的单链表。
在进行插入和删除的时候一定需要注意环状结构不能被破坏了。
二、手搓代码
1. 结构体以及宏定义
# define True 1
# define False 0
typedef struct CycleLink
{
int data;
struct CycleLink* next;
}Clink;
2. 初始化头结点
Clink* CreateClink()
{
Clink* p = (Clink*)malloc(sizeof(Clink));
memset(p, 0, sizeof(Clink));
p->next = p;
return p;
}
3. 判断是否为空
因为他是一个环状结构,判断是否为空我们需要判断头结点的next域
是否指向自身,而不是头结点的next域
是否为空
int IsEmpty(Clink* p)
{
if (p->next == p)
{
return True;
}
return False;
}
4.打印
循环遍历打印,和单链表不同的是终止条件不是这个指针遍历到空就不打印,而是这个指针遍历到头结点不打印。
void PrintClink(Clink* p)
{
if (IsEmpty(p) == True)
{
printf("空表打印个der\n");
return;
}
Clink* tmp = p->next;
while (tmp!= p)
{
printf("%d ", tmp->data);
tmp = tmp->next;
}
putchar('\n');
}
5.头插
头插就是将新结点插入到头结点和首元结点之间,先让新结点的nex域
指向头结点的next域
(如果循环链表中没有元素,就不是插入到头结点和首元结点之间,这样头结点的next域
就指向自己,所以不管是否为空操作一样),然后让头结点的next域
指向新结点。
void InsertHead(Clink* p, int num)
{
assert(p);
Clink* node = (Clink*)malloc(sizeof(Clink));
memset(node, 0, sizeof(Clink));
node->data = num;
node->next = p->next;
p->next = node;
}
6. 尾插
和单链表头插不同的就是新结点的next域
不指向空了,而是指向头结点
void InsertBack(Clink* p, int num)
{
assert(p);
Clink* node = (Clink*)malloc(sizeof(Clink));
memset(node, 0, sizeof(Clink));
node->data = num;
Clink* tmp = p;
while (tmp->next != p)
{
tmp = tmp->next;
}
tmp->next = node;
node->next = p;
}
7.指定位置插入
首先肯定需要判断链表是否存在,然后很重要的是判断下标是否超出链表长度。超出链表首尾长度一切都免谈咯。
当我们知道了链表的总长度之后,我们就可以插入数据咯。方法还是循环遍历链表,然后找到想插入的下标之后插入,修改指针指向,这和单链表的操作一模一样,没什么好说的。因为我这里循环遍历的条件是下一个指针不指向头指针的话下标相同就插入,所以如果想要插入到最后一个元素之后,我们需要调用一下尾插函数。
void InsertPosition(Clink* p, int index, int num)
{
assert(P);
int i = 0;
Clink* tmp = p;
while (tmp->next != p)
{
i++;
tmp = tmp->next;
}//三个结点,下标0,1,2
if (index<0 || index>i)
{
printf("下标小于0或者超出链表长度\n");
return;
}
else if (index == i)
{
InsertBack(p, num);
return;
}
i = 0;
tmp = p;
Clink* node = (Clink*)malloc(sizeof(Clink));
memset(node, 0, sizeof(Clink));
node->data = num;
while (tmp ->next!= p)
{
if (i == index)
{
node->next = tmp->next;
tmp->next = node;
return;
}
i++;
tmp = tmp->next;
}
}
8. 头删
头删和单链表的头删相同,大家可以看我的单链表博客。
实现逻辑就是先让头结点指向首元结点的下一个结点(可能是别的结点也可能是头结点),然后free
掉首元结点就好了。
void PopHead(Clink* p)
{
if (IsEmpty(p) == True)
{
printf("空表无法头删\n");
return;
}
Clink* tmp = p->next;
if (tmp == p)
{
return;
}
p->next = tmp->next;
free(tmp);
tmp = NULL;
}
9. 尾删
尾删和单链表的尾删一模一样,大家可以看我的单链表博客。
尾删就是遍历到最后一个结点,然后倒数第二个结点指向最后一个结点的下一个结点也就是头结点,释放掉最后一个结点就好了。
void PopBack(Clink* p)
{
if (IsEmpty(p) == True)
{
printf("空表无法尾删\n");
return;
}
Clink* tmp1 = p;
Clink* tmp2 = p->next;
while (tmp2->next != p)
{
tmp1 = tmp1->next;
tmp2 = tmp2->next;
}
tmp1->next = tmp2->next;
free(tmp2);
tmp2 = NULL;
}
10.剩余操作
剩余的指定位置删除,指定元素删除,查询元素,更改元素的值都和单链表操作一模一样,唯一不同的是循环遍历的条件不同。单链表循环遍历的终止条件是当下一个结点为空,而循环链表的终止条件是下一个结点为头结点。
这也就是单链表和循环链表的不同和需要注意的地方。
总结
循环链表和单链表的区别就在于单链表的最后一个结点指向空,而循环链表的最后一个结点指向头结点。就这一个区别但是我们要很小心的注意循环遍历条件,否则在进行增删改查操作的时候就会出现问题。