目录
- 前言
- 一、循环链表
- 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是否小于1return ERROR;if (p == *R) //p==R ,条件成立有两种情况,第一,i-1是否恰好等于尾指针指向;第二,i-1大于表长{ if (j != i - 1) //如果i-1大于表长,返回ERRORreturn 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