双向循环链表————遍历、查找、插入结点 (基于C语言实现)

以下是我学习双向循环链表时所用到的函数,其中包括遍历,删除,插入结点等。

下面是我踩过的坑:

  • 遍历函数需要考虑退出循环的情况
  • 头删、指定删、尾删函数要考虑链表为空以及链表中只有一个结点的情况,链表中只有一个结点时,一定要记得将头结点的next指针重新指向头结点自身,否则会段错误!指定删函数需要考虑目标结点是否在链表,以及目标结点在头、在尾、在中间的情况
  • 头插、指定插、尾插函数要考虑链表为空的情况
  • 如需对链表做遍历找到目标结点时,需考虑要不要对头结点做备份!

image

下面是我的函数接口:

定义双向循环链表中的结点的数据的类型

// 指的是双向循环链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int DataType_t;

构造双向循环链表的结点,链表中所有结点的数据类型应该是相同的

typedef struct DoubleCircularLinkedList
{
	DataType_t data;					   // 结点的数据域
	struct DoubleCircularLinkedList *prev; // 直接前驱的指针域
	struct DoubleCircularLinkedList *next; // 直接后继的指针域

} DoubleCirLList_t;

创建一个空双向循环链表,空链表应该有一个头结点,对链表进行初始化

/******************************************************
 *
 *  name      : DoubleCirLList_Create
 *	function  : 创建一个空双向循环链表,空链表应该有一个头结点,对链表进行初始化
 *  argument  :None
 *  retval    : 头结点的地址
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
DoubleCirLList_t *DoubleCirLList_Create(void)
{
	// 1.创建一个头结点并对头结点申请内存
	DoubleCirLList_t *Head = (DoubleCirLList_t *)calloc(1, sizeof(DoubleCirLList_t));
	if (NULL == Head)
	{
		perror("Calloc memory for Head is Failed");
		exit(-1);
	}

	// 2.对头结点进行初始化,头结点是不存储数据域,指针域指向自身即可,体现“循环”
	Head->prev = Head;
	Head->next = Head;

	// 3.把头结点的地址返回即可
	return Head;
}

创建一个新的结点,并对该结点进行初始化(数据域 + 指针域)

/******************************************************
 *
 *  name      : DoubleCirLList_NewNode
 *	function  : 创建一个新的结点,并对该结点进行初始化(数据域 + 指针域)
 *  argument
 * 				@data :需要传入结点中的数据
 *
 *  retval    : 新结点的地址
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
DoubleCirLList_t *DoubleCirLList_NewNode(DataType_t data)
{
	// 1.创建一个新结点并对新结点申请内存
	DoubleCirLList_t *New = (DoubleCirLList_t *)calloc(1, sizeof(DoubleCirLList_t));
	if (NULL == New)
	{
		perror("Calloc memory for NewNode is Failed");
		return NULL;
	}

	// 2.对新结点的数据域和指针域(2个)进行初始化,指针域指向结点自身,体现“循环”
	New->data = data;
	New->prev = New;
	New->next = New;

	return New;
}

遍历链表,打印每个结点中的数据

/******************************************************
 *
 *  name      : PrintfAllNodes
 *	function  : 遍历链表,打印每个结点中的数据
 *  argument
 * 				@Head :头结点的地址
 *
 *  retval    : None
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
void PrintfAllNodes(DoubleCirLList_t *Head)
{
	// 备份头结点
	DoubleCirLList_t *phead = Head;

	// 考虑空链表的情况
	if (Head->next == Head)
	{
		printf("为空链表\n");
		return;
	}

	// 考虑链表中只有一个结点的情况
	if (phead->next == Head->next->next)
	{
		printf("%d\n", phead->next->data);
		return;
	}
	
	while (1)
	{
		phead = phead->next;
		printf("%d\n", phead->data);

		if (phead->next == Head->next)
			break;
	}

	return;
}

将新的结点插入在双向循环链表的头部

/******************************************************
 *
 *  name      : DouCircLList_HeadInsertCircLList_HeadInsert
 *	function  : 将新的结点插入在双向循环链表的头部
 *  argument
 * 				@head : 头结点的地址
 * 			    @data : 结点数据域的数据
 *
 *  retval    : 成功返回1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
bool DouCircLList_HeadInsert(DoubleCirLList_t *Head, DataType_t data)
{
	// 创建新结点,并对新结点初始化
	DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
	if (NULL == New)
	{
		return false;
	}

	// 判断链表是否为空
	if (Head->next == Head)
	{
		// 将头结点的next指针指向新结点
		Head->next = New;
		return true;
	}

	// 链表不为空的情况
	Head->next->prev->next = New; // 将尾结点的next指针指向新结点
	New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
	New->next = Head->next;		  // 将新结点的next指针指向首结点
	Head->next->prev = New;		  // 将首结点的prev指针指向新结点
	Head->next = New;			  // 将头节点的next指针指向新结点

	return true;
}

将新的结点插入在双向循环链表的尾部

/******************************************************
 *
 *  name      : DoubleCirLList_TailInsert
 *	function  : 将新的结点插入在双向循环链表的尾部
 *  argument
 * 				@head : 头结点的地址
 * 			    @data : 结点数据域的数据
 *
 *  retval    : 成功为1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
// 尾插
bool DoubleCirLList_TailInsert(DoubleCirLList_t *Head, DataType_t data)
{
	// 创建新结点,并对新结点初始化
	DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
	if (NULL == New)
	{
		return false;
	}

	// 判断链表是否为空
	if (Head->next == Head)
	{
		// 将头结点的next指针指向新结点
		Head->next = New;
		return true;
	}

	// 列表不为空的情况
	New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
	New->next = Head->next;		  // 将新界点的next指针指向首结点
	New->prev->next = New;		  // 将尾结点的next指针指向新结点
	Head->next->prev = New;		  // 将首结点的prev指针指向新结点

	return true;
}

将新的结点插入在双向循环链表指定结点的后面

/******************************************************
 *
 *  name      : DoubleCircLList_DestInsert
 *	function  : 将新的结点插入在双向循环链表指定结点的后面
 *  argument
 * 				@Head : 头结点的地址
 * 			    @data : 结点数据域的数据
 * 				@destval : 需要插入位置的结点的数据域
 *
 *  retval    : 成功为1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/23
 *  note      : None
 *
 * *******************************************************/
bool DoubleCircLList_DestInsert(DoubleCirLList_t *Head, DataType_t destval, DataType_t data)
{
	// 创建新结点,并对新结点初始化
	DoubleCirLList_t *New = DoubleCirLList_NewNode(data);
	if (NULL == New)
	{
		return false;
	}

	// 判断链表是否为空
	if (Head->next == Head)
	{
		printf("链表为空列表,找不到目标结点,无法插入\n");
		return false;
	}

	// 备份头结点
	DoubleCirLList_t *phead = Head;

	// 遍历链表,找到目标结点
	while (1)
	{
		phead = phead->next;

		if (destval == phead->data)
			break;

		if (phead->next == Head->next)
			break;
	}

	// 判断结点是否在链表中,如不在直接退出
	if (phead->next == Head->next && destval != phead->data)
	{
		printf("找不到目标结点\n");
		return false;
	}

	// 如果目标结点在尾部
	if (phead->next == Head->next)
	{
		New->prev = Head->next->prev; // 将新结点的prev指针指向尾结点
		New->next = Head->next;		  // 将新界点的next指针指向首结点
		New->prev->next = New;		  // 将尾结点的next指针指向新结点
		Head->next->prev = New;		  // 将首结点的prev指针指向新结点
	}
	else // 目标结点不在尾部
	{
		New->next = phead->next; // 将新结点的next指针指向目标结点的直接后继
		New->prev = phead;		 // 将新结点的prev指针指向目标结点
		New->next->prev = New;	 // 将目标结点的直接后继的prev指针指向新结点
		phead->next = New;		 // 将目标结点的next指针指向新结点
	}

	return true;
}

删除链表的首结点

/******************************************************
 *
 *  name      : DoubleCircLList_HeadDel
 *	function  : 删除链表的首结点
 *  argument
 * 				@Head : 头结点的地址
 *
 *  retval    : 成功为1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/24
 *  note      : None
 *
 * *******************************************************/
bool DoubleCircLList_HeadDel(DoubleCirLList_t *Head)
{
	// 判断链表是否为空
	if (Head->next == Head)
	{
		printf("链表为空列表\n");
		return false;
	}

	// 判断链表是否只有一个结点
	if (Head->next->next == Head->next)
	{
		Head->next->next = NULL; // 将首结点的next指针指向NULL
		Head->next->prev = NULL; // 将首结点的prev指针指向NULL
		free(Head->next);		 // 释放首结点
		Head->next = Head;		 // 将头节点的next指针指向头节点  这步非常重要!!!!不做会段错误
	}
	else // 链表不只一个结点的情况
	{

		DoubleCirLList_t *temp = Head->next;	   // 对首结点的地址做备份
		Head->next->prev->next = Head->next->next; // 将尾部结点的next指针指向首结点的直接后继

		Head->next->next->prev = Head->next->prev; // 将首结点的直接后继的prev指针指向尾结点

		Head->next = temp->next; // 将头结点的next指针指向首结点的直接后继

		// 将首结点的next指针和prev指针指向NULL
		temp->next = NULL;
		temp->prev = NULL;

		// 释放首结点
		free(temp);
	}

	return true;
}

删除链表的尾结点

/******************************************************
 *
 *  name      : DoubleCircLList_HailDel
 *	function  : 删除链表的尾结点
 *  argument
 * 				@Head : 头结点的地址
 *
 *  retval    : 成功为1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/23
 *  note      : None
 *
 * *******************************************************/
bool DoubleCircLList_HailDel(DoubleCirLList_t *Head)
{
	// 判断链表是否为空
	if (Head->next == Head)
	{
		printf("链表为空列表\n");
		return false;
	}

	// 判断链表是否只有一个结点
	if (Head->next->next == Head->next)
	{

		Head->next->next = NULL; // 将首结点的next指针指向NULL
		Head->next->prev = NULL; // 将首结点的prev指针指向NULL
		free(Head->next);		 // 释放首结点
		Head->next = Head;		 // 将头节点的next指针指向头节点  这步非常重要!!!!不做会段错误
	}
	else // 链表不只一个结点的情况
	{
		DoubleCirLList_t *temp = Head->next->prev; // 对尾结点做备份
		Head->next->prev->next->next = Head->next; // 将尾结点的直接前驱的next指针指向首结点
		Head->next->prev = temp->next;			   // 将首结点的prev指针指向尾结点的直接前驱
		Head->next->prev->prev = NULL;			   // 将尾结点的prev指针指向NULL
		temp->next = NULL;						   // 将尾结点的next指针指向NULL
		temp->prev = NULL;						   // 将尾结点的prev指针指向NULL
		free(temp);								   // 释放尾结点
	}
	return true;
}

将链表的指定结点删除

/******************************************************
 *
 *  name      : DoubleCircLList_DestNode
 *	function  : 将链表的指定结点删除
 *  argument
 * 				@head : 头结点的地址
 * 				@destval : 需要插入位置的结点的数据域
 *
 *  retval    : 成功为1,否则为0
 *  author    : Dazz
 *  date      : 2024/4/23
 *  note      : None
 *
 * *******************************************************/
bool DoubleCircLList_DestNode(DoubleCirLList_t *Head, DataType_t destval)
{
	// 判断链表是否为空
	if (Head->next == Head)
	{
		printf("链表为空列表\n");
		return false;
	}

	// 备份头结点
	DoubleCirLList_t *phead = Head;
	// 遍历链表,找到目标结点
	while (1)
	{
		phead = phead->next;

		if (destval == phead->data)
			break;

		if (phead->next == Head->next)
			break;
	}

	// 判断目标结点是否在链表中,如不在直接退出
	if (phead->next == Head->next && destval != phead->data)
	{
		printf("找不到目标结点\n");
		return false;
	}

	// 目标结点在链表,判断链表是否只有一个结点,如是,直接删除
	if (Head->next == Head->next->next)
	{
		Head->next->next = NULL; // 将首结点的next指针指向NULL
		Head->next->prev = NULL; // 将首结点的prev指针指向NULL
		free(Head->next);		 // 释放首结点
		Head->next = Head;		 // 将头节点的next指针指向头节点  这步非常重要!!!!不做会段错误
	}
	else // 目标结点在链表,且链表中不只一个结点的情况
	{
		// 目标结点在头的情况
		if (phead == Head->next)
		{
			DoubleCirLList_t *temp = Head->next;	   // 对首结点的地址做备份
			Head->next->prev->next = Head->next->next; // 将尾部结点的next指针指向首结点的直接后继

			Head->next->next->prev = Head->next->prev; // 将首结点的直接后继的prev指针指向尾结点

			Head->next = temp->next; // 将头结点的next指针指向首结点的直接后继

			// 将首结点的next指针和prev指针指向NULL
			temp->next = NULL;
			temp->prev = NULL;

			// 释放首结点
			free(temp);
		}
		// 目标结点在尾的情况
		else if (phead == Head->next->prev)
		{
			DoubleCirLList_t *temp = Head->next->prev; // 对尾结点做备份
			Head->next->prev->next->next = Head->next; // 将尾结点的直接前驱的next指针指向首结点
			Head->next->prev = temp->next;			   // 将首结点的prev指针指向尾结点的直接前驱
			Head->next->prev->prev = NULL;			   // 将尾结点的prev指针指向NULL
			temp->next = NULL;						   // 将尾结点的next指针指向NULL
			temp->prev = NULL;						   // 将尾结点的prev指针指向NULL
			free(temp);								   // 释放尾结点
		}
		// 目标结点在中间的情况
		else
		{
			phead->prev->next = phead->next; // 将目标结点的直接前驱的next指针指向目标结点的直接后继
			phead->next->prev = phead->prev; // 将目标结点的直接后继的prev指针指向目标结点的直接前驱
			phead->next = NULL;				 // 将目标结点的next指针指向NULL
			phead->prev = NULL;				 // 将目标结点的prev指针指向NULL
		}
	}
	return true;
}
posted @ 2024-04-25 00:36  Dazz_24  阅读(101)  评论(0)    收藏  举报