目录
1.循环链表的概念
将单链表中终端结点的指针由空指针改为指向第一个结点,就使单链表形成一个环,这种头尾相连的单链表成为单循环链表,简称循环链表。
下图是循环链表的示意图,这个示意图是有头结点的,所以这种循环链表的操作比较简单,基本跟前面介绍的单链表差不多。为了增加难度,本文的程序全部都是没有头结点的循环链表,但是由于循环链表有他的特性,即使是没有头结点,他的插入和删除同样可以设计的简单巧妙。
2.循环链表的基本操作
(1)初始化:建立一个空的静态链表,链表不存放任何结点。
(2)尾插法创建链表
(3)头插法创建循环链表
(4)查找指定位置的元素:查找第i个结点的元素值。
(5)查找指定元素所在的位置:查找指定元素e在链表中的位置。
(6)插入结点:在指定位置插入新结点。
(7)删除结点:删除指定位置的结点。
(8)删除链表:将链表中所有结点删除,释放全部内存。
(9)遍历链表:将循环链表中所有元素按顺序打印出来。
(10)逆置链表:将链表中所有元素的存放顺序逆置。
(11)查找中间结点:用快慢指针法查找循环链表的中间结点。
(12)拼接链表:将两个循环链表按顺序拼接成一个。
3.循环链表的编程实现
在程序的设计思路上,循环链表的新建、插入和删除等操作跟普通链表差不多,由于本文的循环链表是没有头结点的循环链表,一般来说没有头结点的链表在删除和插入的时候需要将头结点和其他结点分别处理。但是循环链表由于首尾相连,所以我们可以将首元结点的前一个结点当做头结点,即将最末尾的结点当做头结点,这种简单巧妙的操作会给编程带来极大便利。
3.1 C语言实现循环链表
(1)循环链表的声明 cirlinklist.h
#ifndef CIRLINKLIST_H
#define CIRLINKLIST_H
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct Node *Next;
}Node;
typedef Node* linklist;//给节点指针取别名
typedef enum Bool
{
FALSE,TRUE//枚举默认值从0开始,依次加1
}Bool;
//不带头结点的循环链表
Bool InitList(linklist* L);//初始化链表,链表头结点为空
void CreateListEnd(linklist* L,int n);//尾插法创建链表
void CreateListHead(linklist* L,int n);//头插法创建链表
Bool GetElem(linklist L, int i, ElemType* e);//获得第i个位置的元素
int search(linklist L, ElemType e);//查找元素在链表中的位置,如果失败返回0
Bool ListInsert(linklist* L,int i,ElemType e);//在第i个位置插入元素
Bool ListDelete(linklist* L,int i,ElemType* e);//删除第i个元素
Bool ListDeleteAll(linklist* L);//删除全部链表
void traverseList(linklist L);//遍历链表
void Reverse(linklist* L);//逆置链表
int findMiddle(linklist L, ElemType* e);//查找中间结点,将查找的元素值赋给e,并返回该节点是第几个结点,如果是空表则返回0
linklist connect(linklist L1,linklist L2);//L2接在L1后面
#endif // CIRLINKLIST_H
(2) 循环链表的实现 cirlinklist.c
#include "cirlinklist.h"
#include <stdio.h>
Bool InitList(linklist* L)
{
(*L)=NULL;
return TRUE;
}
void traverseList(linklist L)
{
linklist p=L;
if(L==NULL)
{
printf("当前循环链表是空链表 \n\n\n");
return;
}
else
printf("%d ",p->data);//第一个结点的元素值
for(p=p->Next;p!=L;p=p->Next)
{
printf("%d ",p->data);
// p=p->Next;
}
printf("\n\n");
}
void CreateListEnd(linklist* L,int n)
{
linklist p,rear;
int i;
(*L)=(linklist)malloc(sizeof(Node));
if((*L)==NULL)
{
printf("结点申请失败,初始化未完成!\n");
exit(0);
}
(*L)->data=1;//
(*L)->Next=*L;//第一个结点
rear=*L;
for(i=1;i<n;i++)
{
p=(linklist)malloc(sizeof(Node));
p->data=i+1;//rand()%100+1;
p->Next=*L;//新节点是新的尾节点,它指向头结点
rear->Next=p;
rear=rear->Next;
}
}
Bool GetElem(linklist L, int i, ElemType* e)
{
linklist p=L;
int j;
for(j=1;j<i;j++)
{
p=p->Next;
if(p==L)
{
printf("结点位置超过链表最大长度!\n");
return FALSE;
}
}
*e=p->data;
return TRUE;
}
int search(linklist L, ElemType e)
{
linklist p=L;
int j;
for(j=1;p->data!=e;j++)
{
p=p->Next;
if(p==L)
{
printf("查找失败!\n");
return 0;
}
}
return j;
}
Bool ListInsert(linklist* L,int i,ElemType e)
{
linklist p=*L,s;
int j;
for(p=p->Next;p->Next!=*L;p=p->Next);//找到尾结点,p指向尾结点
for(j=1;j<i;j++)//待插入元素会插在p的后面
{
p=p->Next;
if((p==*L)&&(j>1))//最多只能插在最后一个结点后
{
printf("插入位置超出最大范围!\n");
return FALSE;
}
}
s=(linklist)malloc(sizeof(Node));
s->data=e;
s->Next=p->Next;
p->Next=s;
return TRUE;
}
Bool ListDelete(linklist* L,int i,ElemType* e)
{
linklist p,q;
int j;
for(p=*L;p->Next!=*L;p=p->Next);//找到尾结点,p指向尾结点
for(j=1;j<i;j++)//待删除结点在p的后面
{
p=p->Next;
if((p==*L)&&(j>1))//最多只能插在最后一个结点后
{
printf("插入位置超出最大范围!\n");
return FALSE;
}
}
q=p->Next;
p->Next=q->Next;
*e=q->data;
free(q);
return TRUE;
}
void CreateListHead(linklist* L,int n)
{
linklist p,rear;
int i;
*L =(linklist)malloc(sizeof(Node));//第一个结点(也是尾插法的尾结点)
(*L)->data=0;
(*L)->Next=*L;
rear=*L;
for(i=0;i<n-1;i++)
{
p=(linklist)malloc(sizeof(Node));
p->data=i+1;//rand()%100+1;
p->Next=*L;
*L=p;//新节点成为新链表的第一个结点
}
rear->Next=*L;//头尾相连,成为循环链表
}
Bool ListDeleteAll(linklist* L)
{
linklist p=*L,q;
while(p->Next!=*L)//*L存放循环链表第一个结点的地址,第一个结点被删除后,*L指向的地址不变,只是地址里面没有了数据
{
q=p;
p=p->Next;
free(q);
}
free(p);
return TRUE;
}
void Reverse(linklist* L)//翻转链表
{
linklist cur=*L,head=*L,tmp;
while (cur->Next!=head)
{
tmp=cur;
cur=cur->Next;//旧链表指针后移
tmp->Next=*L;//新结点插在新链表最前面
*L=tmp;
}
tmp=cur;
tmp->Next=(*L);//被删掉的元素用头插法插入新的头结点
*L=tmp;
//旧链表的头结点是新链表的尾结点
head->Next=*L;
}
int findMiddle(linklist L, ElemType* e)
{
linklist fast,slow;
int i;
fast=slow=L;
for(i=1;fast->Next->Next!=L&&fast->Next!=L;fast=fast->Next->Next)
{
slow=slow->Next;
i++;
}
*e=slow->data;
return i;
}
linklist connect(linklist L1,linklist L2)
{
linklist p;
for(p=L1;p->Next!=L1;p=p->Next);//找到尾结点,p指向L1的尾结点
p->Next=L2;
for(p=p->Next;p->Next!=L2;p=p->Next);//找到尾结点,p指向L1的尾结点
p->Next=L1;
return L1;
}
(3)测试程序 main.c
#include <stdio.h>
#include "cirlinklist.h"
int main()
{
ElemType a;
linklist list,list1,list2;
int num;
printf(" 1.初始化\n");
InitList(&list);
traverseList(list);
printf(" 2.尾插法新建循环链表\n");
CreateListEnd(&list,10);
traverseList(list);
ListDeleteAll(&list);
InitList(&list);
printf(" 3.头插法新建循环链表\n");
CreateListHead(&list,10);
traverseList(list);
printf(" 4.元素插入\n");
ListInsert(&list,5,10);
traverseList(list);
printf(" 5.元素删除\n");
ListDelete(&list,5,&a);
printf("被删除的元素为:%d \n",a);
traverseList(list);
printf(" 6.指定位置查找元素\n");
GetElem(list,3,&a);
printf("第3个元素为:%d \n\n\n",a);
printf(" 7.指定元素查找位置\n");
num=search(list,6);
printf("6是第%d个元素 \n\n\n",num);
printf(" 8.逆置链表\n");
printf("原链表----------");
traverseList(list);
Reverse(&list);
printf("逆置链表---------");
traverseList(list);
printf("\n\n 9.快慢指针查找中间结点\n");
printf("原链表\n");
traverseList(list);
num=findMiddle(list,&a);
printf("中间结点为%d,这是链表的第%d个结点\n\n\n",a,num);
printf("\n\n 10.拼接链表\n");
printf("拼接前的两个链表\n");
InitList(&list1);
CreateListHead(&list1,10);
traverseList(list1);
CreateListEnd(&list2,10);
traverseList(list2);
list1=connect(list1,list2);
printf("拼接后\n",a,num);
traverseList(list1);
ListDeleteAll(&list1);
return 0;
}
3.2 测试结果
4. 参考资料
[1]小甲鱼的教学视频:https://www.bilibili.com/video/av2975983/?p=18