当前位置: 首页 > news >正文

C语言数据结构(5)双向链表

1. 双向链表的结构

哨兵位不存储有效数据。

当链表中只有哨兵位节点的时候,我们称该链表为空链表,即哨兵位是不能删除的。

注意:这里的“带头”跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头节点。

带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的”

“哨兵位”存在的意义:

遍历循环链表避免死循环。

2. 双向链表的实现

哨兵位:非有效结点。

首结点:第一个有效结点。

尾结点:最后一个有效结点。

2.1 头文件

typedef int LTDataType;
//链表中结点的结构
typedef struct ListNode
{struct ListNode* next; //指针保存下⼀个节点的地址——后继结点指针struct ListNode* prev; //指针保存前⼀个节点的地址——前驱结点指针LTDataType data;
}LTNode;//注意:双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位(单向链表可以直接插入数据)
//注意:当链表中删到只有哨兵位时,称为空链表(不可再删)
//注意:哨兵位是不能删除也不能修改的,即不能对哨兵位进行任何操作,但是可以改变哨兵位的指向(头插:指插入到哨兵位之后,头节点之前,成为新的头节点)//传入参数-改变参数式的初始化方式
//void LTInit(LTNode** pphead);
//接受返回值式的初始化
LTNode* LTInit();void LTDesTroy(LTNode** pphead);
void LTdesTroy(LTNode* phead);   //推荐一级(优:保持接口一致性;缺:调用完后phead需手动置空)void LTPrint(LTNode* phead);//不需要改变哨兵位(的位置),则不需要传二级指针
//需要改变哨兵位(的内容val、next),则不需要传二级指针
//如果需要修改哨兵位(的位置)的话(phead->next),则传二级指针××××××××
//注意:改变phead->next只需要*phead,改变哨兵位需要*pphead
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);//头删、尾删
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);//查找
LTNode* LTFind(LTNode* phead, LTDataType x);//指定插入(之后)
void LTInsert(LTNode* pos, LTDataType x);
//指定删除
void LTErase(LTNode* pos);

2.2 实现代码、测试代码

(1)初始化

函数声明
//List.h
//传入参数-改变参数式的初始化方式
//void LTInit(LTNode** pphead);
//接受返回值式的初始化
LTNode* LTInit();
函数实现
//List.c
//双链表
#include "Double_Linked_List.h"
LTNode* LTBuyNode(LTDataType x) {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL) {perror("malloc fail!");exit(1);}newnode->data = x;//让新节点自循环newnode->next = newnode->prev = newnode;return newnode;
}
//void LTInit(LTNode** pphead) {
//	*pphead = (LTNode*)malloc(sizeof(LTNode));
//	if (*pphead == NULL) {
//		perror("malloc fail!");
//		exit(1);
//	}
//	(*pphead)->data = -1;    //给哨兵位一个无效的数据
//  //带头双向循环链表:初始化哨兵位时,其前驱指针和后继指针都是它自己
//	(*pphead)->next = (*pphead)->prev = *pphead;
//}
LTNode* LTInit() {LTNode* phead = LTBuyNode(-1);return phead;
}
函数测试

(2)打印

函数声明
void LTPrint(LTNode* phead);
函数实现
void LTPrint(LTNode* phead) {//phead不能为空assert(phead);//先走到首结点LTNode* pcur = phead->next;    //phead->next 不会为空,至少也是自己(哨兵位)//循环条件:没到下一个哨兵位while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

(3)头插、尾插

函数声明
//头插、尾插
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);

双链表同样是通过一个结点指针来维护,这个结点指针永远指向头结点——哨兵位,永远不会改变其指向,则不需要传二级指针

逻辑演示

头插是在第一个有效结点之前插入,而不是在哨兵位之前插入——这是尾插

函数实现
//List.c
//双链表//尾插——在最后一个有效节点之后、哨兵位之前插入节点
void LTPushBack(LTNode* phead, LTDataType x) {assert(phead);LTNode* newnode = LTBuyNode(x);//phead phead->prev(ptail)  newnode//生成两条链——修改新结点的指向newnode->next = phead;newnode->prev = phead->prev;//重连两条链——修改尾结点、哨兵位的指向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;
}
函数测试

(4)头删、尾删

函数声明
//头删、尾删
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);

双链表同样是通过一个结点指针来维护,这个结点指针永远指向头结点——哨兵位,永远不会改变其指向,则不需要传二级指针

逻辑演示

函数实现
//List.c
//双链表//尾删
void LTPopBack(LTNode* phead) {assert(phead);//链表为空:只有一个哨兵位节点assert(phead->next != phead);//或者phead->prev != phead//找到待删节点LTNode* tail = phead->prev;//找到待删节点的前驱节点LTNode* tailprev = tail->prev;//互指tailprev->next = phead;phead->prev = tailprev;free(tail);tail = NULL;
}//头删
void LTPopFront(LTNode* phead) {assert(phead);assert(phead->next != phead);//找到待删节点LTNode* del = phead->next;//找到待删节点的后继节点LTNode* next = del->next;//phead del nextnext->prev = phead;phead->next = next;free(del);del = NULL;
}
函数测试

(5)查找pos位置

函数声明
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
函数实现

思路:遍历查找。

//List.c
//双链表//查找pos位置
LTNode* LTFind(LTNode* phead, LTDataType x) {assert(phead);//遍历之前,先走到首结点LTNode* pcur = phead->next;//遍历结束条件:来的第2个哨兵位while (pcur != phead){if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
}
函数测试

(6)在pos位置之后插入

函数声明
//指定插入(之后)
void LTInsert(LTNode* pos, LTDataType x);

双链表同样是通过一个结点指针来维护,这个结点指针永远指向头结点——哨兵位,永远不会改变其指向,则不需要传二级指针

逻辑演示

函数实现
//List.c
//双链表//在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;          //后
}
函数测试

(7)删除pos位置的数据

函数声明
//指定删除
void LTErase(LTNode* pos);

双链表同样是通过一个结点指针来维护,这个结点指针永远指向头结点——哨兵位,永远不会改变其指向,则不需要传二级指针

逻辑演示

函数实现
//List.c
//双链表//删除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;
}
函数测试

(8)销毁

函数声明
//销毁
void LTDesTroy(LTNode** pphead);
void LTdesTroy(LTNode* phead);   
//推荐一级——
//优:保持接口一致性;
//缺:调用完后phead需手动置空
函数实现
//List.c
//双链表//销毁
//void LTDesTroy(LTNode** pphead) {
//	assert(pphead);
//	//哨兵位不能为空
//	assert(*pphead);
//
//  //循环遍历,保存下一个,删除这一个
//	LTNode* pcur = (*pphead)->next;
//	while (pcur != *pphead)
//	{
//		LTNode* next = pcur->next;
//		free(pcur);
//		pcur = next;
//	}
//	//链表中只有一个哨兵位
//	free(*pphead);
//	*pphead = 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;
}
函数测试

3. 顺序表和链表的优缺点分析


http://www.dtcms.com/a/312102.html

相关文章:

  • 深入剖析Nacos:云原生架构的基石
  • Python编程基础与实践:Python基础数据类型入门
  • 中国工程院郑裕国院士确认出席:2025第五届生物发酵营养源高峰论坛生物发酵展
  • CMake基础使用指南
  • QtC++ 调用 tesseract开源库 搭配 Opencv 实现文字识别:从tesseract库基本介绍到实际应用实现
  • 【华为OD机试】计算图形面积
  • 关于Egret引擎的一些思考
  • 单位长度上的RC参数
  • 【补题】Codeforces Round 715 (Div. 1) B. Almost Sorted
  • linux中pthread_t 的值与top -Hp中线程id值的区别
  • 装 饰 器 模 式
  • 深入 Go 底层原理(七):逃逸分析
  • C++ 11 模板萃取
  • 丑数-优先队列/三指针/动态规划
  • Linux 动静态库的制作和使用
  • 深度剖析PyTorch torch.compile的性能曲线与优化临界点
  • SpringBoot 01 IOC
  • PyTorch 张量核心操作——比较、排序与数据校验
  • java实现运行SQL脚本完成数据迁移
  • 通俗易懂解释Java8 HashMap
  • Rust进阶-part1-智能指针概述-box指针
  • 【多模态】DPO学习笔记
  • 嵌入式文件系统
  • Java中Lambda 表达式的解释
  • PCB铜浆塞孔工艺流程
  • 如何快速解决PDF解密新方法?
  • 使用C++实现日志(1)
  • 疏老师-python训练营-Day33 MLP神经网络的训练
  • AbstractExecutorService:Java并发核心模板解析
  • 深入 Go 底层原理(一):Slice 的实现剖析