数据结构 - 线性表 - 双向链表

一.链表的分类

链表的分类有3种分类,一共有8种(2*2*2):

(1)带头和不带头。

(2)单向和双向。

(3)循环和不循环

说明:

(1)单向链表只能从头结点遍历到尾结点,双向链表能从头走向为尾也能从

复制代码

二.常见的两种链表

**1.**单链表 - 不带头单向不循环链表。

**2.**双向链表 - 双向带头循环链表。

三.双向链表的结点

**1.结点组成:**保存的数据 + 指向下一个节点的指针 + 指向前一个结点的指针。

2.结点定义:

复制代码
typedef int LTDataType;
typedef struct ListNode {
	int data;
	struct ListNode* next;//指向后一个节点的指针
	struct ListNode* prev;//指向前一个结点的指针
}LTNode;

四.双向链表节点的创建

用 malloc 申请结点内存,若内存开辟失败就打印错误信息并结束程序,给结点存入数据 x,同时把该结点的 next 和 prev 指针都指向自身,形成自循环,最后返回新结点的地址。

复制代码
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;

	return newnode;
}

五.双向链表的初始化

复制代码
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

六.双向链表的尾插

先断言检查头指针不为空,调用函数生成新结点,把新结点的前驱指向原来的尾结点、后继指向哨兵头结点,再修改原尾结点的后继和头结点的前驱,完成新结点接到链表尾部的双向链接。

复制代码
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;
}

七.双向链表的头插

断言校验哨兵头指针有效后创建新结点,先设置新结点的前后指针,再修改原首结点的前驱和哨兵头的后继,把新结点插入到哨兵头结点之后,成为链表第一个有效节点。

复制代码
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;
} 

八.尾删

先断言确保链表不为空,拿到尾结点地址保存到 del,将尾结点的前一个结点的 next 指向哨兵头,再更新哨兵头的 prev 指向新的尾部结点,释放被删除结点的内存并置空局部指针,完成尾部结点的删除。

复制代码
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;

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

	free(del);
	del = NULL;
}

九.头删

断言保证链表非空,把哨兵头后的第一个有效结点记为待删结点 del,修改后继结点的前驱和哨兵头的后继指针,跳过待删结点,释放该结点内存并将局部指针置空,完成链表头部元素删除。

复制代码
void LTPopFront(LTNode* phead, LTDataType x)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;
	//phead del del->next
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

十.查找

复制代码
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没找到
	return NULL;
}

十一.删除pos位置的结点

校验 pos 结点有效后创建新结点,先设置新结点的前后指针,再修改 pos 原后继结点的前驱与 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;
}

十二.删除pos位置的结点

断言确认待删结点 pos 有效,修改 pos 前后结点的双向指针,让前后结点直接相连绕过 pos,释放 pos 结点内存,将局部 pos 指针置空完成指定结点的删除。

复制代码
void LTErase(LTNode* pos)
{
	assert(pos);
	//pos->prev pos pos->next
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

十三.销毁

复制代码
void LTDestroy(LTNode** pphead)
{
	LTNode* pcur = (*pphead)->next;
	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(*pphead);
	*pphead = NULL;
}
相关推荐
不正经学生4 小时前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
门思科技4 小时前
LoRaWAN 设备类别:Class A、B、C 对比与选型指南
c语言·开发语言·php
M78佐菲6 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
yi.Ist7 小时前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
晊晌_h8 小时前
嵌入式从0到精通——数据结构总结[特殊字符]
数据结构·算法·排序算法
xiaobobo33308 小时前
c语言中for循环条件中定义临时栈变量的作用域
c语言·for循环条件定义栈变量·大括号作用域·独立栈变量
我找到地球的支点啦8 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
惜离殇9 小时前
从零开始的敲代码生活--Linux应用软件(文件操作基础1)
linux·c语言·文件操作·标准io
潘潘的嵌入式日记10 小时前
I²C 从机接收总被覆盖?双缓冲要在 STOP 时交接
c语言·开发语言·单片机