C语言:详解双向链表

C语言:详解双向链表

1.双向链表的结构
2.双向链表的实现

1.双向链表的结构

双向链表是带头双向循环链表。

  • 链表带头的意思是有头节点,且头节点的数据是无效的。
  • 双向指两节点能通过指针互相访问,上一节点可通过next指针找到下一节点,下一节点可通过prev指针找到上一节点。
  • 循环指尾节点能通过next指针找到头节点,双向链表由于具有“双向”的性质,所以头节点能通过prev指针直接找到尾节点。

双向链表的节点由3个部分组成,分别是数据、指向下一节点的指针next、指向上一节点指针prev。

对于单链表,指向第一个节点的指针phead为NULL时,链表为空;对于双向链表,链表只有一个头节点时,为空链表,此时phead->next指向phead,phead->prev指向phead。
在这里插入图片描述

双向链表的结构如图所示:
在这里插入图片描述

2.双向链表的实现

创建3个文件,在头文件List.h中定义节点结构体、进行函数声明、包含所需头文件;在文件List.c中编写函数;最后在test.c中进行测试。

在List.h中,需声明的函数有初始化链表、销毁链表、打印链表、尾/头插、尾/头删、在pos指向的节点后插入、删除pos指向的节点、查找节点。

//List.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode    //定义双向链表节点结构
{
	LTDataType data;       //数据
	struct ListNode* next; //指向下一个节点的指针
	struct ListNode* prev; //指向上一个节点的指针
}LTNode;
LTNode* LTInit();  //初始化

void LTDesTroy(LTNode* phead);//销毁链表

void LTPrint(LTNode* phead);//打印

void LTPushBack(LTNode* phead, LTDataType x);//尾插

void LTPushFront(LTNode* phead, LTDataType x);//头插

void LTPopBack(LTNode* phead);//尾删

void LTPopFront(LTNode* phead);//头删

void LTInsert(LTNode* pos, LTDataType x);//在pos后插入

void LTErase(LTNode* pos);//删pos

LTNode* LTFind(LTNode* phead, LTDataType x);//查找
打印链表

遍历双向链表,即可完成打印。但需要注意,与单链表不同,双向链表的头节点的数据是无效的,所以要从第一个有效节点开始遍历,即指针pcur应赋为phead->next;在循环中用pcur=pcur->next使指针pcur移动,当pcur指向尾节点时,pcur->next又指向头节点,所以循环条件是pcur不能为phead。

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

用malloc函数动态开辟内存空间,定义指向新节点的指针node指向这块空间,赋给所需数据,让新节点的next、prev指针都指向自己,最后返回新节点。

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

用LTBuyNode申请一个数据为-1的新节点,再定义一个指针phead指向这个节点,返回phead。使用时用LTNode类型指针接收phead。

LTNode* LTInit()//初始化
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}
尾插

先让新节点newnode连上头节点和尾节点,即newnode->prev=phead->prev,newnode->next=phead。
在这里插入图片描述
再让尾节点连上newnode,头节点连上newnode,即phead->prev->next=newnode,phead->prev=newnode,两步的先后顺序不可调换,因为执行完phead->prev->next=newnode后,尾节点就是newnode了,phead->prev原来指向d3节点,应改为指向newnode。
在这里插入图片描述

void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;//连上尾节点
	newnode->next = phead;      //连上头节点
	phead->prev->next = newnode;//尾节点连上newnode
	phead->prev = newnode;      //头节点连上newnode
}
头插

新节点应插在头节点和第一个有效节点之间,先让newnode连上头节点和第一个有效节点。
在这里插入图片描述
再让头节点连上newnode,第一个有效节点连上newnode。
在这里插入图片描述

void LTPushFront(LTNode* phead, LTDataType x)//头插
{                                            //插在头节点和第一个有效节点之间
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;      //连上头节点
	newnode->next = phead->next;//连上第一个有效节点
	phead->next->prev = newnode;//第一个有效节点连上newnode
	phead->next = newnode;      //头节点连上newnode    
}
尾删

删除尾节点前要保存尾节点,头节点连上尾节点的上一个节点,尾节点的上一个节点连上头节点。
在这里插入图片描述

void LTPopBack(LTNode* phead)//尾删
{
	assert(phead && phead->next != phead);//链表有效且不只有头节点
	LTNode* del = phead->prev;//保存尾节点
	del->prev->next = phead;  //尾节点的上一个节点连上头节点
	phead->prev = del->prev;  //头节点连上尾节点的上一个节点
	free(del);
	del = NULL;
}
头删

头删删的是第一个有效节点,先头节点连上第一个有效节点的下一节点,再第一个有效节点的下一节点连上头节点。
在这里插入图片描述

void LTPopFront(LTNode* phead)//头删
{                             //删第一个有效节点
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;//保存第一个有效节点
	phead->next = del->next;  //头节点连上第一个有效节点的下一节点
	del->next->prev = phead;  //第一个有效节点的下一节点连上头节点
	free(del);
	del = NULL;
}
查找

从第一个有效节点开始遍历链表,若pcur->data==x,即找到了。

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

pos指向指定的节点,先让新节点newnode连上pos指向的节点,以及连上pos指向节点的下一节点。
在这里插入图片描述
接下来,先让pos->next指向节点连上新节点,再让pos连上新节点。
在这里插入图片描述

void LTInsert(LTNode* pos, LTDataType x)//在pos后插入数据
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
}
删除pos

指向pos指向节点的上一节点的指针为pos->prev,指向pos指向节点的下一节点的指针为pos->next,在删除pos指向节点前把上下两节点连起来,用free释放掉pos,即可删除pos。
在这里插入图片描述

void LTErase(LTNode* pos)//删除pos
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;// pos->prev  pos  pos->next
	free(pos);
	pos = NULL;
}
销毁链表

遍历链表,循环一次销毁一个节点,遍历链表时应及时保存下一节点,避免销毁节点后找不到下一节点。

void LTDesTroy(LTNode* phead)//销毁链表
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

List.c文件中代码如下:

//List.c
#include"List.h"
void LTPrint(LTNode* phead)//打印
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}
LTNode* LTBuyNode(LTDataType x)//申请节点
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = node;
	node->prev = node;
	return node;
}
LTNode* LTInit()//初始化
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}
void LTPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;//连上尾节点
	newnode->next = phead;      //连上头节点
	phead->prev->next = newnode;//尾节点连上newnode
	phead->prev = newnode;      //头节点连上newnode
}
void LTPushFront(LTNode* phead, LTDataType x)//头插
{                                            //插在头节点和第一个有效节点之间
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;      //连上头节点
	newnode->next = phead->next;//连上第一个有效节点
	phead->next->prev = newnode;//第一个有效节点连上newnode
	phead->next = newnode;      //头节点连上newnode    
}
void LTPopBack(LTNode* phead)//尾删
{
	assert(phead && phead->next != phead);//链表有效且不只有头节点
	LTNode* del = phead->prev;//保存尾节点
	del->prev->next = phead;  //尾节点的上一个节点连上头节点
	phead->prev = del->prev;  //头节点连上尾节点的上一个节点
	free(del);
	del = NULL;
}
void LTPopFront(LTNode* phead)//头删
{                             //删第一个有效节点
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;//保存第一个有效节点
	phead->next = del->next;  //头节点连上第一个有效节点的下一节点
	del->next->prev = phead;  //第一个有效节点的下一节点连上头节点
	free(del);
	del = 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; //没找到
}
void LTInsert(LTNode* pos, LTDataType x)//在pos后插入数据
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next->prev = newnode;
	pos->next = newnode;
}
void LTErase(LTNode* pos)//删除pos
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;// pos->prev  pos  pos->next
	free(pos);
	pos = NULL;
}
void LTDesTroy(LTNode* phead)//销毁链表
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}
测试

在test.c文件中测试上述代码。

例如:创建节点的数据为1,2,4的双向链表。

//test.c
#include"List.h"
void test1()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 2);
	LTPushFront(phead, 1);
	LTPushBack(phead, 4);
	LTPrint(phead);
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
在此基础上创建节点为1,2,3,4的链表。

//test.c
#include"List.h"
void test1()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 2);
	LTPushFront(phead, 1);
	LTPushBack(phead, 4);
	LTNode* ret1 = LTFind(phead, 2);
	LTInsert(ret1, 3);
	LTPrint(phead);
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

拙作一篇,望诸位同道不吝斧正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值