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

news/2024/10/21 6:07:30/

目录

  • 1、双向链表的结构
  • 2、顺序表和双向链表的优缺点分析
  • 3、双向链表的实现

1、双向链表的结构

在这里插入图片描述

注意:这⾥的“带头“跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了更好的理解就直接称为单链表的头节点。
带头链表里的头节点,实际为“放哨的”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的”。
“哨兵位”存在的意义:遍历循环链表避免死循环。

2、顺序表和双向链表的优缺点分析

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持O(N)
任意位置插⼊或者删除元素可能需要搬移元素,效率低只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储和频繁访问任意位置频繁插入和删除

3、双向链表的实现

ListNode.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//定义双向链表节点的结构
typedef int Ltdatatype;
typedef struct ListNode
{Ltdatatype data;struct ListNode* prev;//指向前一个节点的指针struct ListNode* next;//指向后一个节点的指针
}ListNode;
//双向链表的初始化
ListNode* LtInit();
//尾插
//不改变哨兵位的地址,所以传一级即可
void LtPushBack(ListNode* phead, Ltdatatype x);//插入数据之前,链表必须初始化到只有一个头结点的情况
//打印链表
void LtPrint(ListNode* phead);
//头插
void LtPushFront(ListNode* phead, Ltdatatype x);
//尾删
LtPopBack(ListNode* phead);
//头删
LtPopFront(ListNode* phead);
//查找
ListNode* LtFind(ListNode* phead, Ltdatatype x);
//指定位置前插入
void LtInsert(ListNode* pos, Ltdatatype x);
//删除pos位置
void LtErase(ListNode* pos);
//销毁链表
void LtDestroy(ListNode* phead);

ListNode.c

#define _CRT_SECURE_NO_WARNINGS
#include "ListNode.h"
//申请节点
ListNode* LtBuyNode(Ltdatatype x)
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));if (node == NULL){perror("malloc fail");exit(1);}//申请成功node->data = x;node->next = node->prev = node;return node;
}
//双向链表的初始化
ListNode* LtInit()
{ListNode*phead = LtBuyNode(-1);return phead;
}
//尾插
void LtPushBack(ListNode* phead, Ltdatatype x)
{assert(phead);ListNode* newnode = LtBuyNode(x);//改变新节点的指向newnode->prev = phead->prev;newnode->next = phead;//改变尾节点和哨兵位的指向phead->prev->next = newnode;phead->prev = newnode;
}
//打印链表
void LtPrint(ListNode* phead)
{ListNode* pcur = phead->next;//遍历链表while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
//头插
void LtPushFront(ListNode* phead,Ltdatatype x)
{assert(phead);ListNode* newnode = LtBuyNode(x);newnode->prev = phead;newnode->next = phead->next;//修改哨兵位和第一个有效节点的指向phead->next->prev = newnode;phead->next = newnode;
}
//尾删
LtPopBack(ListNode* phead)
{//链表必须有效且链表不能为空(只有一个哨兵位)assert(phead && phead->next != phead);ListNode* Del = phead->prev;//尾节点ListNode* DelPrev = Del->prev;//尾节点的前一个节点phead->prev = DelPrev;DelPrev->next = phead;free(Del);//删除Del节点Del = NULL;
}
//头删
LtPopFront(ListNode* phead)
{//判断链表是否有效和链表是否为空assert(phead && phead->next != phead);ListNode* Del = phead->next;//第一个有效节点ListNode* DelNext = Del->next;//有效节点的下一个节点phead->next = DelNext;DelNext->prev = phead;free(Del);//删除Del节点Del = NULL;
}
//查找
ListNode* LtFind(ListNode* phead, Ltdatatype x)
{ListNode* pcur = phead->next;//遍历链表while (pcur != phead){if (pcur->data == x)return pcur;pcur = pcur->next;//继续让pcur往下遍历}return NULL;//没有找到
}
//在pos位置之前插入数据
void LtInsert(ListNode* pos,Ltdatatype x)
{ListNode* newnode = LtBuyNode(x);newnode->prev = pos->prev;newnode->next = pos;pos->prev->next = newnode;pos->prev = newnode;
}
//删除pos位置
void LtErase(ListNode* pos)
{assert(pos);ListNode* PosPrev = pos->prev;//pos的前一个节点ListNode* PosNext = pos->next;//pos的后一个节点PosPrev->next = PosNext;PosNext->prev = PosPrev;free(pos);//pos = NULL;这里就算置空了,也不会影响实参
}
//销毁链表
void LtDestroy(ListNode* phead)
{ListNode* pcur = phead->next;//边遍历边释放节点while (pcur != phead){ListNode* Next = pcur->next;//保存要释放掉节点的下一个地址free(pcur);pcur = Next;}//此时pcur指向phead,而phead还没有被销毁free(phead);pcur = NULL;
}

text.c

#define _CRT_SECURE_NO_WARNINGS
#include "ListNode.h"
void LtnodeTest()
{//测试初始化ListNode* plist = LtInit();//测试尾插LtPushBack(plist,1);LtPushBack(plist,2);LtPushBack(plist,3);//测试打印LtPrint(plist);//测试头插//LtPushFront(plist,4);//LtPushFront(plist,5);//LtPushFront(plist,6);//LtPrint(plist);//测试尾删LtPopBack(plist);LtPrint(plist);//测试头删//LtPopFront(plist);//LtPrint(plist);//测试查找//ListNode*find = LtFind(plist,2);//if (find)//	printf("找到了!\n");//else//	printf("没找到!\n");//测试在pos位置之前插入数据//LtInsert(find,88);//LtPrint(plist);//测试删除pos位置//LtErase(find);//find = NULL;//形参的改变不会影响实参,所以要在函数调用结束之后置为空//LtPrint(plist);//测试销毁链表//LtDestroy(plist);//plist = NULL;
}
int main()
{LtnodeTest();return 0;
}

如果对你有所帮助的话,别忘了一键三连哟,谢谢宝子们😘!


http://www.ppmy.cn/news/1447329.html

相关文章

汽车信息安全入门总结(2)

目录 1.引入 2.汽车信息安全技术 3.密码学基础知识 4.小结 1.引入 上篇汽车信息安全入门总结(1)-CSDN博客主要讲述了汽车信息安全应该关注的点&#xff0c;以及相关法规和标准&#xff0c;限于篇幅&#xff0c;继续聊信息安全相关技术以及需要掌握的密码学基础知识。 2.汽…

Linux 第十七章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

微信小程序常用的api

基础API&#xff1a; wx.request&#xff1a;用于发起网络请求&#xff0c;支持GET、POST等方式&#xff0c;是获取网络数据的主要手段。wx.showToast&#xff1a;显示消息提示框&#xff0c;通常用于向用户展示操作成功、失败或加载中等状态。wx.showModal&#xff1a;显示模态…

打水问题(贪心算法)

题目&#xff1a;有n个人排队到r个水龙头去打水&#xff0c;他们装满水桶的时间t1、t2………tn为整数且各不相等&#xff0c;应如何安排他们的打水顺序才能使他们总共花费的时间最少&#xff1f;通过键盘输入排队打水的人数以及每人打水的时间和水龙头数&#xff0c;使用贪心算…

Spring Data JPA数据批量插入、批量更新真的用对了吗

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 前言 在前两篇文章已经介绍过&#xff0c;在使用Spring Data JPA时&#xff0c;DAO层的Respository通过继承J…

Rust特征对象

一、特征对象是什么&#xff0c;有什么用&#xff0c;怎么用 1、特征对象是什么 特征对象指向实现了 某种特征的类型的实例&#xff0c;这种映射关系是存储在一张表中&#xff0c;可以在运行时通过特征对象找到具体调用的类型方法 可以通过 & 引用或者 Box 智能指针的方式…

TiDB系列之:使用TiUP部署TiDB集群最新版本,同时部署TiCDC的详细步骤

TiDB系列之:使用TiUP部署TiDB集群最新版本,同时部署TiCDC的详细步骤 一、部署TiDB集群二、准备环境三、安装 TiUP四、安装TiUP cluster组件五、初始化包含TiCDC的TiDB集群拓扑文件六、检查和修复集群存在的潜在风险七、查看可以安装的tidb版本八、部署 TiDB 集群:九、查看集…

探索和构建 LLaMA 3 架构:深入探讨组件、编码和推理技术(九)Transformer架构

探索和构建 LLaMA 3 架构&#xff1a;深入探讨组件、编码和推理技术&#xff08;九&#xff09;Transformer架构 Llama Transformer架构 现在将所有单独的组件堆叠到Transformer中&#xff1a; class ModelArgs:dim: int 4096n_layers: int 32n_heads: int 32 # Number o…