数据结构-循环链表和双向链表

前言

本篇文章介绍数据结构中的循环链表和双向链表

一、循环链表

1.1 循环链表的介绍

将单链表的形式稍作改变,单链表的最后一个结点指向第一个结点
对第一个结点概念的说明:
对于不带头结点的链表,第一个结点指首元结点(如图1.1所示)
在这里插入图片描述

图1.1 不带头结点

对于带头结点的链表,第一个结点指头结点(如图1.2所示)
在这里插入图片描述

图1.2 带头结点

本篇文章以带头结点的循环链表为研究对象
与单链表相比,循环链表没有增加新的存储空间,但是,从循环链表中任一结点出发,都能访问到所有结点。
带头结点的循环链表又有两种表示方式,分别是
头指针表示循环链表:头指针指向头结点(如图1.3所示)
在这里插入图片描述

图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所示)
在这里插入图片描述

图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的存储位置:Rnextnextan的存储位置:Ra1an的时间复杂度都为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;  //假设作为单链表类型

  1. 创建空表
    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;
    }
    
  2. 销毁循环链表
    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;
    }
    
  3. 循环链表的插入
    step1 查找 a i − 1 a_{i-1} ai1的存储位置p
    step2 使用flag标记 a i − 1 a_{i-1} ai1的存储位置是否恰好为尾指针*R指向的位置
    step3 生成一个数据域为e的结点newNode
    step4 插入新结点,newNode指针域指向 a i a_i ai a i − 1 a_{i-1} ai1指针域指向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;
    }
    
  4. 循环链表的删除
    step1 查找 a i − 1 a_{i-1} ai1的存储位置 p p p
    step2 保存要删除的结点存储位置 q q q,即 q = p → n e x t q = p \rightarrow next q=pnext
    step3 使结点 a i − 1 a_{i-1} ai1的指针域指向 a i + 1 a_{i+1} ai+1,即 p → n e x t = q → n e x t p \rightarrow next = q \rightarrow next pnext=qnext
    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;
    }
    
  5. 循环链表的建立(头插法)
    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;
    }
    
  6. 合并两个循环链表
    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.1 双向链表结点结构

为了方便处理,使用带头结点的双向链表
为了减少边缘情况的判断,使用两个辅助结点分别作为伪头结点和伪尾结点,这两个伪结点不存储数据
在这里插入图片描述

图2.2 带两个辅助结点的双向链表

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;
  1. 初始化双向链表
    创建两个伪结点,形成双向链表,表示空双向链表
    在这里插入图片描述

    图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;
    }
    
  2. 销毁双向链表

    //销毁双向链表
    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;
    }
    
  3. 获取第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;
    }
    
  4. 在第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;
    }
    
  5. 在第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;
    }
    
  6. 删除第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;
    }
    
    

三、循环双链表

与循环单链表类型,在双向链表的基础上,使双向链表的第一个结点的前驱指向最后一个结点,最后一个结点的后继指向第一个结点,构成循环双链表。
在这里插入图片描述

图3.1 带头结点的循环双链表

循环双链表有一个性质,若 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 prearprior=p=ppriorrear

总结

完整代码:https://gitee.com/PYSpring/data-structure/tree/master

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值