循环链表的定义及其特点;
定义
循环链表是一种特殊的链表数据结构,它与普通链表的区别在于尾节点的下一个指针指向头节点,形成一个环形结构。循环链表可以通过头节点访问到所有的节点,并且可以在常量时间内插入和删除节点。
循环链表的定义包含一个头节点和一个尾节点,头节点用于指示链表的起始位置,尾节点用于标识循环链表的结束位置。每个节点包含一个数据域和一个指针域,指针域指向下一个节点。
循环链表的优点是可以节省内存空间,因为不需要额外的指针来指向尾节点。同时,循环链表可以很方便地实现循环访问和遍历操作,例如循环队列和循环缓冲区等。
在循环链表中,插入和删除节点的操作与普通链表类似,只需要更新相应的指针即可。需要注意的是,在插入和删除节点时,需要确保循环链表的头节点和尾节点的指针正确更新,以保持循环链表的完整性。
注:图示是方便理解的例图,实际数量和结点并非一模一样
特点
- 循环性:循环链表的最后一个节点的指针指向链表的头节点,形成一个闭环。这使得循环链表可以无限循环地访问其中的节点,而无需遍历整个链表。
- 灵活性:由于循环链表可以在常量时间内插入和删除节点,因此它非常适用于需要频繁修改链表结构的场景。插入和删除节点只需要修改相邻节点的指针,而不需要遍历整个链表。
- 空间效率:循环链表不需要额外的指针来指向尾节点,相比于普通链表,可以节省一些内存空间。同时,循环链表可以通过头节点访问到所有的节点,避免了使用额外的指针来遍历链表。
- 循环访问:循环链表可以很方便地实现循环访问和遍历操作。通过头节点,可以轻松地遍历所有节点,而无需关心链表的长度。
- 应用广泛:循环链表在实际应用中有着广泛的用途。例如,循环队列、循环缓冲区和循环链表本身就是循环链表的应用。此外,循环链表还可以用于模拟循环的场景,如音乐播放器的播放列表等。
循环链表的运算
二级指针指在 对地址实现值传递 实现对地址的改变
结构体
typedef int DataType;
/*简单链表的定义*/
typedef struct node
{
DataType data; /*数据域*/
struct node *next; /*指针域*/
}SingleLinkList, SingleLinkNode;
初始化
C 库函数 *void malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
C 库函数 – malloc() | 菜鸟教程 (runoob.com)
通过动态内存分配,可以创建一个空的循环链表。首先分配一个头节点,并将其指针域指向自身,形成一个空的循环链表。
/*1. 初始化*/
int init(SingleLinkList **Head)
{
if(1)
{
/*申请内存*/
(*Head) = (SingleLinkList*)malloc(sizeof(SingleLinkList));
/*判断内存申请是否成功*/
if(*Head == NULL)
{
printf("申请内存错误, 初始化失败![100001]\n");
return 100001;
}
/*循环链表 next 指向组件 */
(*Head)->next = *Head;
return 0;
}
else
{
printf("该链表已经初始化!请删除后再执行此操作![100002]\n");
return 100002;
}
}
插入
在循环链表中插入一个新节点,可以在指定位置之前或之后插入。插入节点时,需要更新相邻节点的指针,使其指向新插入的节点。
注:图示是方便理解的例图,实际数量和结点并非一模一样
头插法
通过循环链表头插法,新插入的节点将成为新的头节点,而原来的节点则按照原来的顺序向后移动。
/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x)
{
SingleLinkNode *newNode;
if(0)
{
printf("链表未初始化![100003]\n");
return 100003;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
if(!newNode)
{
printf("申请节点内存空间失败![100004]\n");
return 100004;
}
newNode->data = x;
newNode->next = (*Head)->next;
(*Head)->next = newNode;
return 0;
}
尾插法
通过循环链表尾插法,新插入的节点将成为新的尾节点,而原来的节点则按照原来的顺序向后移动。
/*2. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x)
{
SingleLinkNode *newNode;
SingleLinkNode *p;
if(0)
{
printf("链表未初始化![100003]\n");
return 100003;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
if(!newNode)
{
printf("申请节点内存空间失败![100004]\n");
return 100004;
}
/* 新结点赋值 next 指向 Head */
newNode->data = x;
newNode->next = *Head;
p = (*Head);
// 遍历到最后一个结点
while(p->next != (*Head))
{
p = p->next;
}
// 把最后一个结点 next(Head) 指向 newNode
p->next = newNode;
return 0;
}
位置i处插入元素x
循环链表在位置i处插入元素x的步骤包括创建新节点、定位到位置i的前一个节点、插入新节点并更新指针。通过这些操作,我们可以在循环链表的指定位置插入新元素。
/*2. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x)
{
int j;
SingleLinkNode *p;
SingleLinkNode *newNode;
/*对i进行判断,0<i<=length+1*/
if(i<1 || i>length(*Head)+1)
{
printf("位置i不是链表有效位置![100005]\n");
return 100005;
}
p = (*Head);
j = 1;
// 遍历到指向位置
while(j<i)
{
j++;
p = p->next;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
/*此处省略检测newNode是否申请成功*/
newNode->data = x;
newNode->next = p->next;
p->next = newNode;
return 0;
}
删除
删除节点:在循环链表中删除指定位置的节点,需要更新相邻节点的指针,使其跳过被删除的节点。同时,需要释放被删除节点的内存空间。
注:图示是方便理解的例图,实际数量和结点并非一模一样
/*3. 删除元素, 删除值为x的元素*/
int delete(SingleLinkList **Head, DataType x)
{
int i;
int j;
SingleLinkNode *p;
SingleLinkNode *q; /*要删除的元素x*/
i = find(*Head,x);
if(!i)
{
printf("元素x【%d】不存在!100006\n", x);
return 100006;
}
p = (*Head);
j=1;
while(j<i)
{
j++;
p = p->next;
}
q = p->next;
p->next = q->next;
free(q); /*释放内存*/
return 0;
}
遍历
链表长度
遍历循环链表:通过头节点,可以轻松地遍历循环链表中的所有节点,不断累加 len 得到长度。
/*4. 链表长度 */
int length(SingleLinkList *Head)
{
int len=0;
SingleLinkNode *p;
p = Head->next;
while(p!=Head)
{
len++;
p = p->next;
}
return len;
}
输出链表
遍历循环链表:通过头节点,可以轻松地遍历循环链表中的所有节点。从头节点开始,依次访问每个节点,并根据需要进行相应的操作。
/*6.输出链表*/
void print(SingleLinkList *Head)
{
SingleLinkNode *p;
int i=0;
p = Head->next;
printf("%d == %d\n", p, Head->data);
printf("%d == %d\n", p, Head);
//
if(p==Head)
{
printf("链表为空!\n");
return;
}
while(p!=Head)
{
printf("Node[%d]. = %d\n", ++i, p->data);
p = p->next;
}
}
判断链表是否存在该值
遍历循环链表:通过头节点,可以轻松地遍历循环链表中的所有节点。从头节点开始,依次访问每个节点,直到访问到存在的值,遍历完找不到则返回下标。
/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x)
{
int i;
SingleLinkNode *p;
i = 1;
p = Head->next;
while( p!= Head && p->data != x)
{
i++;
p = p->next;
}
if(p->next == Head)
{
return 0;
}
else
{
return i;
}
}
循环链表的实现
完整代码
项目结构
main.c
SingleLinkList.c
SingleLinkList.h
welcome.h
项目文件
main.c
#include <stdio.h>
#include <string.h>
#include "SingleLinkList.h"
#include "welcome.h"
int main(int argc, char* argv[])
{
SingleLinkList *Head;
DataType x;
int i,m,n,cmd;
for(i=0;i<strlen(welcome);i++)
{
printf("%c",welcome[i]);
for(m=0;m<1000;m++)
for(n=0;n<1000;n++)
{
;
}
}
printf("-----------简单链表演示程序----------\n");
do
{
printf("1. 初始化链表表\n");
printf("2. 插入元素(头插法)\n");
printf("3. 插入元素(尾插法)\n");
printf("4. 插入元素(在位置i插入)\n");
printf("5. 查找元素x\n");
printf("6. 求链表长度\n");
printf("7. 输出链表\n");
printf("8. 删除元素\n");
printf("10. 帮助\n");
printf("0. 退出\n");
printf("请输入您要进行的操作(1~6,0退出):");
scanf("%d", &cmd);
switch(cmd)
{
case 1:
if(!init(&Head))
{
printf("链表已初始化!\n");
}
break;
case 2:
printf("请输入插入元素x:x=");
scanf("%d",&x);
if(!insert_head(&Head,x))
{
printf("元素(%d)已插入\n", x);
}
break;
case 3:
printf("请输入插入元素x:x=");
scanf("%d",&x);
if(!insert_tail(&Head,x))
{
printf("元素(%d)已插入\n", x);
}
break;
case 4:
printf("请输入插入元素位置i和元素x(i,x):");
scanf("%d,%d", &i, &x);
if(!insert(&Head, i, x))
{
printf("已在位置(%d)插入元素(%d)!\n",i, x);
}
break;
case 5:
printf("请输入要查找的元素x:");
scanf("%d", &x);
if(i = find(Head,x))
{
printf("元素%d存在,在链表位置%d.\n", x, i);
}
else
{
printf("在链表中未找到元素x。\n");
}
break;
case 6:
printf("链表的长度为:%d\n", length(Head));
break;
case 7:
print(Head);
break;
case 8:
printf("请输入要删除的元素x:");
scanf("%d", &x);
if(!delete(&Head, x))
{
printf("元素x【%d】已删除!\n", x);
}
break;
case 10:
printf(" 本程序为链表的演示程序,有XXX设计开发,程序完成了。。。。功能!。。。\n");
break;
}
}while(cmd != 0);
return 0;
}
SingleLinkList.c
/*
SingleLinkList.c
*/
#include "SingleLinkList.h"
#include <stdlib.h>
#include <stdio.h>
/*1. 初始化*/
int init(SingleLinkList **Head)
{
if(1)
{
/*申请内存*/
(*Head) = (SingleLinkList*)malloc(sizeof(SingleLinkList));
/*判断内存申请是否成功*/
if(*Head == NULL)
{
printf("申请内存错误, 初始化失败![100001]\n");
return 100001;
}
(*Head)->next = *Head;
return 0;
}
else
{
printf("该链表已经初始化!请删除后再执行此操作![100002]\n");
return 100002;
}
}
/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x)
{
SingleLinkNode *newNode;
if(0)
{
printf("链表未初始化![100003]\n");
return 100003;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
if(!newNode)
{
printf("申请节点内存空间失败![100004]\n");
return 100004;
}
newNode->data = x;
newNode->next = (*Head)->next;
(*Head)->next = newNode;
return 0;
}
/*2. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x)
{
SingleLinkNode *newNode;
SingleLinkNode *p;
if(0)
{
printf("链表未初始化![100003]\n");
return 100003;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
if(!newNode)
{
printf("申请节点内存空间失败![100004]\n");
return 100004;
}
newNode->data = x;
newNode->next = *Head;
p = (*Head);
while(p->next != (*Head))
{
p = p->next;
}
p->next = newNode;
return 0;
}
/*2. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x)
{
int j;
SingleLinkNode *p;
SingleLinkNode *newNode;
/*对i进行判断,0<i<=length+1*/
if(i<1 || i>length(*Head)+1)
{
printf("位置i不是链表有效位置![100005]\n");
return 100005;
}
p = (*Head);
j = 1;
while(j<i)
{
j++;
p = p->next;
}
newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
/*此处省略检测newNode是否申请成功*/
newNode->data = x;
newNode->next = p->next;
p->next = newNode;
return 0;
}
/*3. 删除元素, 删除值为x的元素*/
int delete(SingleLinkList **Head, DataType x)
{
int i;
int j;
SingleLinkNode *p;
SingleLinkNode *q; /*要删除的元素x*/
i = find(*Head,x);
if(!i)
{
printf("元素x【%d】不存在!100006\n", x);
return 100006;
}
p = (*Head);
j=1;
while(j<i)
{
j++;
p = p->next;
}
q = p->next;
p->next = q->next;
free(q); /*释放内存*/
return 0;
}
/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x)
{
int i;
SingleLinkNode *p;
i = 1;
p = Head->next;
while( p!= Head && p->data != x)
{
i++;
p = p->next;
}
if(p->next == Head)
{
return 0;
}
else
{
return i;
}
}
/*4. 链表长度*/
int length(SingleLinkList *Head)
{
int len=0;
SingleLinkNode *p;
p = Head->next;
while(p!=Head)
{
len++;
p = p->next;
}
return len;
}
/*6.输出链表*/
void print(SingleLinkList *Head)
{
SingleLinkNode *p;
int i=0;
p = Head->next;
if(p==Head)
{
printf("链表为空!\n");
return;
}
while(p!=Head)
{
printf("Node[%d]. = %d\n", ++i, p->data);
p = p->next;
}
}
SingleLinkList.h
/*
SingleLinkList.h
*/
typedef int DataType;
/*简单链表的定义*/
typedef struct node
{
DataType data; /*数据域*/
struct node *next; /*指针域*/
}SingleLinkList, SingleLinkNode;
/*1. 初始化*/
int init(SingleLinkList **Head);
/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x);
/*2. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x);
/*2. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x);
/*3. 删除元素, 删除值为x的元素*/
int delete(SingleLinkList **Head, DataType x);
/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x);
/*6. 求链表的长度 */
int length(SingleLinkList *Head);
/*7.输出链表*/
void print(SingleLinkList *Head);
welcome.h
char welcome[] = "\n\
/\ \n\
( *)======/\==== \n\
)( / \ \n\
__________/ ) / \ \n\
\___ / / \"\" \ \n\
\____ _/ / (**) \ \n\
/ \__/ (----------) \n\
/____|__//_ ( 送给您- ) \n\
| ( 亲爱的 ) \n\
| ( )\n\
| (____)\n\
_|__\n\
\\ ☆新年 . 快乐☆\n\n";
运行结果
小结
循环链表是一种特殊的链表,它的最后一个节点指向第一个节点,形成一个循环的结构。相比于普通链表,循环链表可以更方便地进行循环操作,常用于环形队列、约瑟夫问题等场景。
下面是循环链表的一些应用:
-
应用:
- 环形队列:使用循环链表实现的队列,可以循环利用空间,实现高效的入队和出队操作。
- 约瑟夫问题:使用循环链表可以模拟约瑟夫问题,即在固定规则下,依次淘汰指定位置的节点,直到只剩下一个节点为止。
循环链表是一种特殊的链表,最后一个节点指向第一个节点,形成一个循环结构。它具有插入和删除操作高效、遍历方便等特点,常用于环形队列、约瑟夫问题等场景。使用循环链表可以简化问题的处理,并提高算法的效率。
Josephu问题 - 城北有个混子 - 博客园 (cnblogs.com)