前言
本篇文章介绍数据结构中的循环链表和双向链表
一、循环链表
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 {
ElemType data; //数据域
struct DoubleNode* rlink; //双向链表结点的后继
struct DoubleNode* llink; //双向链表结点的前驱
};
typedef struct DoubleNode DoubleNode;
typedef struct DoubleNode* PDoubleNode;
//双向链表的头结点结构定义
struct DoubleList {
PDoubleNode dummyHead; //双向链表的伪头结点
PDoubleNode dummyRear; //双向链表的伪尾结点
};
typedef struct DoubleList DoubleList;
typedef struct DoubleList* PDoubleList;
-
初始化双向链表
创建两个伪结点,形成双向链表,表示空双向链表
图2.3 空双向链表 //初始化双向链表 void initDoubleList(PDoubleList doubleList) { assert(doubleList); doubleList->dummyHead = (PDoubleNode)malloc(sizeof(DoubleNode)); doubleList->dummyRear = (PDoubleNode)malloc(sizeof(DoubleNode)); //形成双向链表 doubleList->dummyHead->rlink = doubleList->dummyRear; //伪头结点的后继指向伪尾结点 doubleList->dummyHead->llink = NULL; doubleList->dummyRear->llink = doubleList->dummyHead; //伪尾结点的前驱指向伪头结点 doubleList->dummyRear->rlink = NULL; }
-
销毁双向链表
//销毁双向链表 void destroyDoubleList(PDoubleList doubleList) { assert(doubleList); for (PDoubleNode pdn = doubleList->dummyHead; pdn != NULL; NULL) { PDoubleNode pfn = pdn; pdn = pdn->rlink; free(pfn); } doubleList->dummyHead = NULL; doubleList->dummyRear = NULL; }
-
获取第i个数据元素的存储位置
//获取第i个结点 PDoubleNode locateElem(PDoubleList doubleList, int i) { assert(doubleList); assert(i > 0); int countNode = 1; //结点计数 PDoubleNode pdn = doubleList->dummyHead->rlink; //双向链表的第一个结点 while (pdn != doubleList->dummyRear) { if (countNode == i) { return pdn; } else { pdn = pdn->rlink; countNode++; } } return NULL; }
-
在第i个结点前插入一个新结点
step1 寻找第i结点的位置,pdn指向该结点
step2 生成新结点newNode,将newNode的rlink指向pdn结点,newNode->rlink = pdn
; 将newNode的llink指向pdn的前驱结点,newNode->llink = pdn->llink
; 将pdn的前驱结点rlink指向newNode,pdn->llink->rllink = newNode
;将pdn的llink指向newNode,pdn->llink = pdn
图2.4 双向链表的插入 //在第i个结点前插入一个新结点 int insertDoubleNodeBefore(PDoubleList doubleList, int i, ElemType data) { assert(doubleList); PDoubleNode pdn = locateElem(doubleList, i); if (NULL == pdn) { return ERROR; } PDoubleNode newNode = (PDoubleNode)malloc(sizeof(DoubleNode)); newNode->data = data; //在pdn结点前插入新结点 newNode->rlink = pdn; newNode->llink = pdn->llink; pdn->llink->rlink = newNode; pdn->llink = newNode; return SUCCESS; }
-
在第i个结点后插入一个新结点
//在第i个结点后插入一个新结点 int insertDoubleNodeAfter(PDoubleList doubleList, int i, ElemType data) { assert(doubleList); PDoubleNode pdn = locateElem(doubleList, i); if (NULL == pdn) { return ERROR; } PDoubleNode pnn = (PDoubleNode)malloc(sizeof(DoubleNode)); pnn->data = data; //在pdn结点后插入新结点 pnn->llink = pdn; pnn->rlink = pdn->rlink; pdn->rlink->llink = pnn; pdn->rlink = pnn; return SUCCESS; }
-
删除第i个数据结点
step1 寻找第i个数据结点的位置,pdn指向该数据结点
step2 将pdn结点的前驱结点的rlink
指向pdn结点的后继结点,pdn->llink->rlink = pdn->rlink
;将pdn结点的后继结点的llink指向pdn的前驱结点,pdn->rlink->llink = pdn->llink
step3 释放pdn结点的内存,free(pdn)
图2.5 双向链表的删除 //删除第i个数据元素 int deleteDoubleNode(PDoubleList doubleList, int i) { assert(doubleList); PDoubleNode pdn = locateElem(doubleList, i); if (NULL == pdn) { return ERROR; } pdn->llink->rlink = pdn->rlink; pdn->rlink->llink = pdn->llink; free(pdn); return SUCCESS; }
三、循环双链表
与循环单链表类型,在双向链表的基础上,使双向链表的第一个结点的前驱指向最后一个结点,最后一个结点的后继指向第一个结点,构成循环双链表。
循环双链表有一个性质,若
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