数据结构(C语言版)--链表--超详细讲解(2)【带图解】

双链表的基本操作实现

双链表节点的定义

双链表的每个节点包含三个部分:数据域、前驱指针和后继指针。

typedef struct DListNode {
    int data;
    struct DListNode* prev;
    struct DListNode* next;
} DListNode;

申请新节点

1、动态申请节点内存:
使用 malloc 函数为新节点(LTNode 类型)分配内存。
检查内存分配是否成功:若 malloc 返回 NULL(表示内存分配失败),则用 perror 打印错误信息(“malloc fail!”),并通过 exit(1) 终止程序。
2、初始化节点数据与指针:
将传入的参数 x 赋值给新节点的数据域(node->data = x)。
把新节点的 next(指向下一节点的指针)和 prev(指向前一节点的指针)都指向节点自身(node->next = node->prev = node),这是循环双向链表的核心特点 —— 单个节点形成自循环(后续添加更多节点时,会打破自循环并构建成多节点的循环链)。
3、返回新节点:
完成初始化后,返回新创建节点的指针,供循环双向链表的其他操作(如插入、删除等)使用。

//申请节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;

	return node;
}

双链表的初始化

1、函数定义:LTNode* LTInit() 函数的作用是初始化双向链表,返回类型为指向 LTNode 类型的指针(即链表头节点的指针)。
2、创建哨兵位头节点:
调用 LTBuyNode(-1) 函数来创建一个新节点。这里传入 -1 作为节点的初始数据,这个节点会作为双向链表的哨兵位头节点(哨兵位节点不存储实际业务数据,主要用于简化链表操作,比如统一空链表和非空链表的操作逻辑)。
将创建好的哨兵位节点的指针赋值给 phead。
3、返回头节点:函数最后返回 phead,即双向链表的哨兵位头节点指针,后续对双向链表的操作(如插入、删除等)都可以基于这个头节点展开。

//初始化

LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}
打印双链表

1、定义一个指针pcur,让它指向哨兵位头节点(phead)的下一个节点(即链表的第一个有效节点)。
2、通过while (pcur != phead)循环遍历链表(只要pcur没有回到哨兵位头节点,就继续遍历,因为双向链表是循环的,哨兵位头节点是循环的 “终点” 标志)。
3、在循环中,打印当前节点(pcur)的数据(格式为“数据->”),然后让pcur指向下一个节点(pcur = pcur->next;),继续打印下一个节点。
4、当循环结束(pcur回到哨兵位头节点,说明遍历完链表了),打印一个换行符\n。

//打印
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
尾插

1、首先用assert(phead);确保传入的头节点指针phead有效(不是NULL)。
2.、调用LTBuyNode(x)创建一个包含数据x的新节点。
3.、进行指针调整,将新节点插入到链表尾部:
  新节点的prev指针指向原来的尾节点(phead->prev,因为phead是哨兵位头节点,它的prev指向链表的尾节点)。
  新节点的next指针指向哨兵位头节点phead。
  原来的尾节点的next指针指向新节点(phead->prev->next = newnode;)。
  哨兵位头节点的prev指针指向新节点(phead->prev = newnode;),这样新节点就成为了新的尾节点。

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead phead->prev newnode
	newnode->prev = phead->prev;
	newnode->next = phead;

	phead->prev->next = newnode;
	phead->prev = newnode;
}
头插

1、首先用assert(phead);确保传入的头节点指针phead有效(不是NULL)。
2、 调用LTBuyNode(x)创建一个包含数据x的新节点。
3.、进行指针调整,将新节点插入到链表头部:
  新节点的next指针指向哨兵位头节点原本的下一个节点(phead->next)。
  新节点的prev指针指向哨兵位头节点phead。
  哨兵位头节点原本的下一个节点的prev指针指向新节点(phead->next->prev = newnode;)。
  哨兵位头节点的next指针指向新节点(phead->next = newnode;),这样新节点就成为了链表的第一个有效节点。

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead newnode phead->next
	newnode->next = phead->next;
	newnode->prev = phead;

	phead->next->prev = newnode;
	phead->next = newnode;
}
尾删

此时让phead->prev原本要指向d3(del)现在让它指向d2(del->prev)

del->prev->next(d2->d3)原本要指向d3现在让它指向phead

1、首先用assert(phead && phead->next != phead);确保链表有效且不为空(即不是只有哨兵位头节点)。
2、找到要删除的尾节点,用del指针指向它(LTNode* del = phead->prev;,因为phead是哨兵位头节点,它的prev指向链表的尾节点)。
3、进行指针调整,将尾节点从链表中移除:
  尾节点的前一个节点的next指针指向哨兵位头节点(del->prev->next = phead;)。
  哨兵位头节点的prev指针指向尾节点的前一个节点(phead->prev = del->prev;)。
4、释放被删除的尾节点的内存(free(del);),并将del指针置为NULL,防止野指针。

//尾删
void LTPopBack(LTNode* phead)
{
	//链表必须有效且链表不能为空(只有一个哨兵位)
	assert(phead && phead->next != phead);

	LTNode* del = phead->prev;
	//phead del->prev del
	del->prev->next = phead;
	phead->prev = del->prev;

	//删除del节点
	free(del);
	del = NULL;
}
头删

1、首先用assert(phead && phead->next != phead);确保链表有效且不为空(即不是只有哨兵位头节点)。
2、找到要删除的头节点(哨兵位头节点的下一个节点),用del指针指向它(LTNode* del = phead->next;)。
3、进行指针调整,将该头节点从链表中移除:
  哨兵位头节点的next指针指向del的下一个节点(phead->next = del->next;)。
  del的下一个节点的prev指针指向哨兵位头节点(del->next->prev = phead;)。
4、释放被删除的头节点的内存(free(del);),并将del指针置为NULL,防止野指针。

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	
	LTNode* del = phead->next;
	
	//phead del del->next
	phead->next = del->next;
	del->next->prev = phead;

	//删除del节点
	free(del);
	del = NULL;
}

查找

1、定义一个指针 pcur,从哨兵位头节点的下一个节点(即链表的第一个有效节点)开始。
2、通过 while (pcur != phead) 循环遍历链表(只要 pcur 没有回到哨兵位头节点,就继续遍历,因为双向链表是循环的,哨兵位头节点是循环的 “终点” 标志)。
3、在循环中,判断当前节点(pcur)的数据是否等于要查找的 x:
  如果等于,直接返回当前节点的指针(return pcur;)。
  如果不等于,让 pcur 指向下一个节点(pcur = pcur->next;),继续查找。
4、如果循环结束(pcur 回到哨兵位头节点,说明遍历完链表了),还没找到,就返回 NULL。

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}
在指定位置之后插入数据

1、首先用assert(pos);确保指定的位置pos有效(不是NULL)。
2、调用LTBuyNode(x)创建一个包含数据x的新节点。
3、进行指针调整:
  先让新节点的next指针指向pos节点原本的下一个节点(newnode->next = pos->next;)。
  再让新节点的prev指针指向pos节点(newnode->prev = pos;)。
  然后让pos节点原本的下一个节点的prev指针指向新节点(pos->next->prev = newnode;)。
  最后让pos节点的next指针指向新节点(pos->next = newnode;)。这样就完成了在pos节点之后插入新节点的操作。

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;
}
删除指定节点

1、首先用assert(pos);确保要删除的节点pos有效(不是NULL)。
2、 进行指针调整,将pos节点从链表中移除:
  让pos节点的下一个节点的prev指针指向pos节点的前一个节点(pos->next->prev = pos->prev;)。
   让pos节点的前一个节点的next指针指向pos节点的下一个节点(pos->prev->next = pos->next;)。
3、释放pos节点的内存(free(pos);),并将pos指针置为NULL,防止野指针

//删除pos节点
void LTErase(LTNode* pos)
{
	//pos理论上来说不能为phead,但是没有参数phead,无法增加校验
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}
销毁双链表

1、首先用assert(phead);确保头节点指针phead有效(不是NULL)。
2、定义一个指针pcur,从哨兵位头节点的下一个节点(即链表的第一个有效节点)开始。
3、通过while (pcur != phead)循环遍历链表(只要pcur没有回到哨兵位头节点,就继续遍历)。
4、在循环中,先保存pcur的下一个节点到next指针中,然后释放pcur节点的内存,再让pcur指向next节点,继续释放下一个节点。
5、当循环结束(pcur回到哨兵位头节点),释放哨兵位头节点的内存,并将phead指针置为NULL,防止野指针。

//销毁
void LTDesTroy(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//此时pcur指向phead,而phead还没有被销毁
	free(phead);
	phead = NULL;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值