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

2024-04-28 14:44

本文主要是介绍C语言:数据结构(双向链表),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 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;
}

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

这篇关于C语言:数据结构(双向链表)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/943520

相关文章

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

Go语言中使用JWT进行身份验证的几种方式

《Go语言中使用JWT进行身份验证的几种方式》本文主要介绍了Go语言中使用JWT进行身份验证的几种方式,包括dgrijalva/jwt-go、golang-jwt/jwt、lestrrat-go/jw... 目录简介1. github.com/dgrijalva/jwt-go安装:使用示例:解释:2. gi