目录
- 前言
- 一、循环链表
- 1.1 循环链表的介绍
- 1.2 循环链表的实现
- 二、双向链表
- 2.1 双向链表的介绍
- 2.2 双向链表的实现
- 三、循环双链表
- 总结
前言
本篇文章介绍数据结构中的循环链表和双向链表
一、循环链表
1.1 循环链表的介绍
将单链表的形式稍作改变,单链表的最后一个结点指向第一个结点
对第一个结点概念的说明:
对于不带头结点的链表,第一个结点指首元结点(如图1.1所示)
对于带头结点的链表,第一个结点指头结点(如图1.2所示)
本篇文章以带头结点的循环链表为研究对象
与单链表相比,循环链表没有增加新的存储空间,但是,从循环链表中任一结点出发,都能访问到所有结点。
带头结点的循环链表又有两种表示方式,分别是
头指针表示循环链表:头指针指向头结点(如图1.3所示)
头指针表示循环单链表 { 找 a 1 的时间复杂度 O ( 1 ) 找 a n 的时间复杂度 O ( n ) 头指针表示循环单链表 \begin{cases} 找a_1的时间复杂度O(1) \\ 找a_n的时间复杂度O(n) \end{cases} 头指针表示循环单链表{找a1的时间复杂度O(1)找an的时间复杂度O(n)
尾指针表示循环链表:尾指针指向最后一个结点(如图1.4所示)
尾指针表示循环单链表 { a 1 的存储位置: R → n e x t → n e x t a n 的存储位置: R 找 a 1 和 a n 的时间复杂度都为 O ( 1 ) 尾指针表示循环单链表 \begin{cases} a_1的存储位置:R \rightarrow next \rightarrow next \\ a_n的存储位置:R \\ 找a_1和a_n的时间复杂度都为O(1) \end{cases} 尾指针表示循环单链表⎩ ⎨ ⎧a1的存储位置:R→next→nextan的存储位置:R找a1和an的时间复杂度都为O(1)
1.2 循环链表的实现
这里使用带头结点的循环链表,并且使用尾指针表示循环链表。
循环链表的定义和表示
//定义返回值常量
#define SUCCESS 1
#define ERROR 0
//假设数据元素类型为char
typedef char ElemType;
//定义结点类型
struct Node;
typedef struct Node* PNode; //假设作为结点指针类型
struct Node {
ElemType data; //数据域
PNode next; //指针域
};
typedef struct Node* LinkList; //假设作为单链表类型
-
创建空表
step1 使用malloc()函数创建一个sizeof(struct Node)大小的空间作为头结点
step2 将头结点的指针域指向自身,表示一个空表//4.1 创建一个空表,返回头指针 LinkList createNullList_loopLink(void) { LinkList linkList = (LinkList)malloc(sizeof(struct Node)); if (NULL == linkList) { printf("malloc fail!\n"); return NULL; } linkList->next = linkList; //头结点的指针域指向自身表示空表 return linkList; }
-
销毁循环链表
step1 首先从头结点开始销毁
step2 最后销毁尾指针指向的结点//4.2 销毁一个循环单链表 void destroyList_loopLink(LinkList* R) { assert(R && *R); PNode phead = (*R)->next; while (phead != *R) //销毁除了尾结点的其余结点 { PNode q = phead->next; free(phead); phead = q; } //销毁尾结点 free(*R); *R = NULL; }
-
循环链表的插入
step1 查找 a i − 1 a_{i-1} ai−1的存储位置p
step2 使用flag标记 a i − 1 a_{i-1} ai−1的存储位置是否恰好为尾指针*R指向的位置
step3 生成一个数据域为e的结点newNode
step4 插入新结点,newNode指针域指向 a i a_i ai, a i − 1 a_{i-1} ai−1指针域指向newNode
step5 根据flag的值改变R的指向,flag=1,*R = newNode//4.5 在第i个元素之前插入数据元素e int insertElem_loopLink(LinkList* R, int i, ElemType e) { assert(R && *R); PNode p = (*R)->next; //头指针 int j = 0; int flag = 0; //用于标记位置i-1是否恰好等于尾指针指向的结点 while (p != *R && j < i - 1) //寻找第i-1位置的结点 { p = p->next; j++; } if (j > i - 1) //判断i是否小于1 return ERROR; if (p == *R) //p==R ,条件成立有两种情况,第一,i-1是否恰好等于尾指针指向;第二,i-1大于表长 { if (j != i - 1) //如果i-1大于表长,返回ERROR return ERROR; else //i-1位置恰好在尾指针指向的结点 flag = 1; } //新建结点 PNode newNode = (PNode)malloc(sizeof(struct Node)); if (NULL == newNode) { printf("malloc fail!\n"); return ERROR; } newNode->data = e; newNode->next = p->next; p->next = newNode; if (flag) *R = newNode; return SUCCESS; }
-
循环链表的删除
step1 查找 a i − 1 a_{i-1} ai−1的存储位置 p p p
step2 保存要删除的结点存储位置 q q q,即 q = p → n e x t q = p \rightarrow next q=p→next
step3 使结点 a i − 1 a_{i-1} ai−1的指针域指向 a i + 1 a_{i+1} ai+1,即 p → n e x t = q → n e x t p \rightarrow next = q \rightarrow next p→next=q→next
step4 判断 q q q的指向是否与 ∗ R *R ∗R的指向相同,若相同,则 ∗ R = p *R = p ∗R=p
step5 free(q),返回SUCCESS//4.6 删除第i个元素 int deleteElem_loopLink(LinkList* R, int i) { assert(R && *R); PNode p = (*R)->next; int j = 0; while (p != *R && j < i - 1) { p = p->next; j++; } if (p == *R || j > i - 1) return ERROR; PNode q = p->next; p->next = q->next; if (q == *R) //判断删除结点是否为尾指针指向的结点 *R = p; free(q); return SUCCESS; }
-
循环链表的建立(头插法)
step1 新建头结点phead并将phead的指针域指向自身
step2 定义尾指针R,并指向phead
step3 循环新建结点,插入头结点之后
step4 判断插入的结点是否为第一个,若是,则R = newNode
step5 最后返回R//4.7 循环单链表的建立(头插法) //带头结点 //返回尾指针 LinkList createLoopLinkList_head(ElemType* element, int length) { assert(element); //创建头结点 LinkList phead = (LinkList)malloc(sizeof(struct Node)); if (NULL == phead) { printf("malloc fail!\n"); return NULL; } phead->next = phead; PNode R = phead; //声明尾指针 int i = 0; for (; i < length; i++) { //新建结点 PNode newNode = (PNode)malloc(sizeof(struct Node)); if (NULL == newNode) { printf("malloc fail!\n"); free(phead); return NULL; } newNode->data = element[i]; newNode->next = phead->next; phead->next = newNode; if (newNode->next == phead) //判断是否为第一个插入结点 { R = newNode; } } return R; }
-
合并两个循环链表
step1 保存Ta的头指针
step2 Ta的尾指针指向Tb的首元结点
step3 释放Tb的头结点
step4 Tb的尾指针指向Ta的头结点//4.8 合并两个循环单链表(Tb合并在Ta之后) //返回合并后的尾指针 LinkList mergeLoopLinkList(LinkList Ta, LinkList Tb) { if (NULL == Ta) return Tb; if (NULL == Tb) return Ta; PNode head_Ta = Ta->next; //保存Ta的头指针 Ta->next = Tb->next->next; //Ta尾指针指向Tb的首元结点 free(Tb->next); //释放Ta的头结点 Tb->next = head_Ta; //Tb的指针域指向Ta的头结点 return Tb; }
二、双向链表
2.1 双向链表的介绍
在双向链表中,每个结点除了数据域以外,还有两个指针域,一个指向其前驱结点,另一个指向其后继结点。每个结点的结构可以形象地描述如下:
为了方便处理,使用带头结点的双向链表
2.2 双向链表的实现
双向链表的结构及其结构定义如下
//定义返回值常量
#define SUCCESS 1
#define ERROR 0
//定义双链表及其结点
//数据元素结构
typedef char ElemType;
struct DoubleNode; //双链表结点
typedef struct DoubleNode* PDoubleNode; //结点指针类型
struct DoubleNode { //双链表结点结构
ElemType data; //数据域
PDoubleNode prior; //前驱指针
PDoubleNode rear; //后继指针
};
typedef struct DoubleNode* DoubleLinkList; //假设为双链表类型
-
创建空表
当头结点的两个指针域为NULL时,表示空表//5.1 创建空双向链表 DoubleLinkList createNullList_doubleLink(void) { //创建头结点 DoubleLinkList doubleLinkList = (DoubleLinkList)malloc(sizeof(struct DoubleNode)); if (NULL == doubleLinkList) { printf("malloc fail\n"); return NULL; } doubleLinkList->prior = NULL; doubleLinkList->rear = NULL; return doubleLinkList; }
-
获取第i个数据元素的存储位置
//5.3 获取第i个数据元素的位置 PDoubleNode locateElem_doubleLink(DoubleLinkList doubleLinkList, int i) { assert(doubleLinkList); PDoubleNode p = doubleLinkList->rear; int j = 1; while (p && j < i) { p = p->rear; j++; } if (!p || j > i) //判断i的合法性 return NULL; return p; }
-
双向链表的插入
step1 找到第i个位置的结点存储位置p
step2 创建新结点 n e w N o d e newNode newNode, n e w N o d e → p r i o r = p → p r i o r , p → p r i o r → r e a r = n e w N o d e , n e w N o d e → r e a r = p , p → p r i o r = n e w N o d e newNode \rightarrow prior = p \rightarrow prior,p \rightarrow prior \rightarrow rear=newNode,\\newNode \rightarrow rear = p,p \rightarrow prior = newNode newNode→prior=p→prior,p→prior→rear=newNode,newNode→rear=p,p→prior=newNode//5.4 双向链表的插入 //在第i个位置前插入数据元素 int insertElem_doubleLink(DoubleLinkList doubleLinkList, int i, ElemType e) { assert(doubleLinkList); PDoubleNode p = NULL; p = locateElem_doubleLink(doubleLinkList, i); if (!p) return ERROR; PDoubleNode newNode = (PDoubleNode)malloc(sizeof(struct DoubleNode)); if (NULL == newNode) { printf("malloc fail!\n"); return ERROR; } newNode->data = e; newNode->prior = p->prior; //新结点的前驱指向p的前驱 p->prior->rear = newNode; //p的前驱结点的后继指向新结点 newNode->rear = p; p->prior = newNode; return SUCCESS; }
-
双向链表的删除
step1 找到第i个结点的存储位置p
step2 p → p r i o r → r e a r = p → r e a r , p → r e a r → p r i o r = p → p r i o r p \rightarrow prior \rightarrow rear = p \rightarrow rear,p \rightarrow rear \rightarrow prior = p \rightarrow prior p→prior→rear=p→rear,p→rear→prior=p→prior//5.5 双向链表的删除 //删除第i个数据元素 int deleteElem_doubleLink(DoubleLinkList doubleLinkList, int i) { assert(doubleLinkList); PDoubleNode p = NULL; p = locateElem_doubleLink(doubleLinkList,i); if (!p) return ERROR; p->prior->rear = p->rear; p->rear->prior = p->prior; free(p); return SUCCESS; }
-
双向链表的建立(尾插法)
//5.6 双向链表的建立(尾插法) DoubleLinkList createDoubleLinkList_tail(ElemType* element, int length) { assert(element); //建立头结点 DoubleLinkList doubleLinkList = (DoubleLinkList)malloc(sizeof(struct DoubleNode)); if (NULL == doubleLinkList) { printf("malloc fail!\n"); return NULL; } doubleLinkList->prior = NULL; doubleLinkList->rear = NULL; PDoubleNode tail = doubleLinkList; //定义尾指针 int i = 0; for (i = 0; i < length; i++) { //创建数据元素结点 PDoubleNode newNode = (PDoubleNode)malloc(sizeof(struct DoubleNode)); if (NULL == newNode) { printf("malloc fail!\n"); free(doubleLinkList); return NULL; } newNode->data = element[i]; newNode->rear = NULL; newNode->prior = NULL; tail->rear = newNode; newNode->prior = tail; tail = newNode; } return doubleLinkList; }
三、循环双链表
与循环单链表类型,在双向链表的基础上,使双向链表的第一个结点的前驱指向最后一个结点,最后一个结点的后继指向第一个结点,构成循环双链表。
循环双链表有一个性质,若
p
p
p为指向链表中某结点的指针,则总有
p
→
r
e
a
r
→
p
r
i
o
r
=
p
=
p
→
p
r
i
o
r
→
r
e
a
r
p\rightarrow rear \rightarrow prior = p = p \rightarrow prior \rightarrow rear
p→rear→prior=p=p→prior→rear
总结
完整代码:https://gitee.com/PYSpring/data-structure/tree/master