目录
循环链表
特点:表中最后一个结点的指针域指向头结点,整个链表形成一个环。
既然是环的话,那就不难想象,从表中任意一个结点出发都可以找到表中其他结点。
一、循环单链表
仍旧使用该例子,我们要如何存储用循环单链表实现它呢?
id | name | description |
---|---|---|
1 | 史强 | 最强辅助 |
2 | 章北海 | 我不需要钢印,我是自己思想的主人 |
3 | 罗辑 | 人类不感谢罗辑 |
4 | 维德 | 失去人性,失去很多。失去兽性,失去一切 |
循环单链表与单链表唯一的区别就是多了一个由尾结点指向头结点的指针。
所以循环链表的操作与单链表基本一致
唯一的差别就是循环的条件不是p
或p->next
是否为空,而是它们是否等于头指针(p==L
或p->next==L
)。
循环单链表示例图(含头结点):
循环单链表逻辑图(含头结点):
1、循环单链表定义
步骤一:声明数据元素类型
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
步骤二:声明结点
typedef struct CNode{
ElemType data;//数据域
struct CNode *next;//指针域
}CNode,*CLinkList;
2、循环单链表操作
与单链表相比,循环单链表有两处操作与单链表不同。
一是:尾结点的指针域指向头结点
二是:遍历时,条件变成了p->next
是否为头指针
这里只详细展示两个具体操作
初始化表。构造一个空表。
/*初始化*/
int InitList_CLink(CLinkList *L){
*L=(CLinkList)malloc(sizeof(CNode));//创建头结点,并让头指针指向头结点
if (!(*L)) return FALSE;//分配失败
(*L)->next=(*L);//初始化为头指针
return TRUE;//初始化成功
}
空表逻辑图:
求表长
/*求表长*/
int Length_CLink(CLinkList L){
int i=0;//空表返回0
CNode *s=L;
while (s->next!=L){
//遍历,直到尾结点,空表不进入循环
s=s->next;//向后移动一个
i++;//表长加一
}
return i;//返回表长
}
3、含尾指针的循环单链表
有时候在循环单链表中设立尾指针而不设头指针,可以时有些操作简化。
比如合并两个循环单链表时
二、循环双链表
循环双链表的逻辑图(含头结点):
1、循环双链表定义
步骤一:声明数据元素类型
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
步骤二:声明结点
typedef struct CDNode{
ElemType data;//数据域
struct CDNode *prior;//指针域,指向前驱结点
struct CDNode *next;//指针域,指向后继结点
}CDNode,*CDLinkList;
2、循环双链表操作
循环双链表与循环单链表唯一的区别就是多了一个指向前驱的指针。
所以循环双链表的操作与循环单链表也基本一致
就是对结点操作时,需要多操作一步,让p->prior
指向前驱结点。
这里展示相对于循环单链表有变化的操作。
初始化表。构造一个空表。
/*初始化*/
int InitList_CLink(CDLinkList *L){
*L=(CDLinkList)malloc(sizeof(CDNode));//创建头结点,并让头指针指向头结点
if (!(*L)) return FALSE;//分配失败
(*L)->prior=(*L);//初始化为头指针
(*L)->next=(*L);//初始化为头指针
return TRUE;//初始化成功
}
空表逻辑图:
根据数组创建双链表
1.头插法
/*头插法创建循环双链表*/
int create_HeadInsert(CDLinkList *L,ElemType a[],int n){
// InitList_CLink(L);
CDNode *s;//用来指示新创建的结点
int i;
for(i=n-1;i>=0;i--){
//由于头插法是倒序插入,所以这里我们从数组最后开始遍历
s=(CDLinkList)malloc(sizeof(CDNode));//创建一个新结点
s->data=a[i];//为结点赋值
s->next=(*L)->next;//让该结点指向第一个结点
(*L)->next->prior=s;//第一个结点的前驱指向该结点
s->prior=(*L);//该节点的前驱指向头结点
(*L)->next=s;//让头结点指向该结点
}
}
2.尾插法
/*尾插法创建循环双链表*/
int create_TailInsert(CDLinkList *L,ElemType a[],int n){
// InitList_CLink(L);
CDNode *s,*r=(*L); //s用来指示新创建的结点,r用来移动以连接链表
int i;
for(i=0;i<n;i++){
//遍历数组
s=(CDLinkList)malloc(sizeof(CDNode));//创建一个新结点
s->data=a[i];//为结点赋值
r->next=s;//r始终指向链表的尾端,这里将新结点连接到r的后面
s->prior=r;//该结点的前驱指向r
r=s;//r移动到链表尾端
}
r->next=(*L);//结束时,最后一个结点的指针域赋值为头指针
}
插入操作。在表L中的第i个位置上插入指定元素e。
/*插入*/
int ListInsert_CDLink(CDLinkList *L,int i,ElemType e){
int j=0;//空表序号为0
CDNode *p=(*L);//p指向头结点
CDNode *s;//s用来指示新创建的结点
while (p->next!=(*L)&&j<i-1){
//找到被插入序号的前一个位置
p=p->next;//向后移动一个
j++;
}
if(p==(*L)||(i