【数据结构】C语言实现双链表

news2024/10/9 16:22:45

目录

前言

双链表节点定义

接口函数实现

初始化函数

创建节点

打印双链表

尾插节点

尾删节点

 头插节点

 头删节点

 指定位置前插入

 删除指定位置节点

改写插入删除 

判断链表是否为空

计算链表长度

销毁链表

双链表完整代码

浅谈链表及顺序表


前言

前面我们已经实现了无头单向非循环链表,现在我们来实现带头双向循环链表。

双链表节点定义

//双链表节点定义
typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

与单链表不同的是,在实现双链表时,我们需要创建一个初始化函数。双链表的初始化需要一个头节点,并且这个头节点的prev指针和next指针需要指向自身。

接口函数实现

初始化函数

初始化函数创建了一个头节点(哨兵卫),这个哨兵卫不储存值,并且让它的prev、next指向自身。这样就形成了一个闭环。

//初始化双链表
LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    //prev与next均指向自身
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

 与单链表的实现类似,我们同样也实现两个函数用来检测和辅助其他函数的实现。

创建节点

//创建节点、
LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
    //创建后的节点prev与next均指向NULL
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

新创建的节点的prev与next指针我们让他们指向NULL就可以了,因为后面我们还会处理它的链接关系,所以指向空指针就可以了。 

打印双链表

//打印双链表
void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

PS:

        打印链表信息之所以从phead->next开始是因为我们的phead并没有存值,链表的节点信息是从phead->next开始的。

尾插节点

//尾插节点
void LPushBack(LNode* phead,LDataType x)
{
    //防止传入空指针
	assert(phead);
    //先将x存入创建的节点中
	LNode* newnode = BuyListNode(x);
    //链表最后一个节点就是phead的prev指针
	LNode* tail = phead->prev;
    //将newnode节点链接到链表中
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
}

        可能会有人会问,为什么双链表在尾插的时候传的参数是一级指针啊?单链表在尾插节点的的时候传的就是二级指针。那是因为我们现在实现的双链表具有头节点(哨兵卫),正是因为有了它,我们就不用传二级指针了。为什么了,前面我们讲过,单链表之所以传入的是二级指针。那是因为在特殊情况下我们的第一个节点会变成空指针。有哨兵卫后就不同了,假如我们现在双链表中只存在一个节点,我们将它删除,只需要将phead的prev与next均指向自身就可以了,而如果没有哨兵卫,我们就要将phead置成空指针,这样传入的参数就改变了,那么就需要传二级指针。

        其实就一点,如果链表有哨兵卫的话就直接传入一级指针就可以了。

尾删节点

//尾删节点
void LPopBack(LNode* phead)
{
	assert(phead);
    //防止传入的链表是一个空链表
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;
    //每个节点都是动态开辟出来的,记得释放
	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
}

 头插节点

//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);
	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;
}

 头删节点

//头删节点
void LPopFront(LNode* phead)
{
	assert(phead);
    //防止链表为空
	assert(phead->next != phead);
	LNode* first = phead->next;
	LNode* newfirst = first->next;

	free(first);
	phead->next = newfirst;
	newfirst->prev = phead;
}

 指定位置前插入

//指定位置前插入
void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;
   
	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

 删除指定位置节点

//删除指定位置节点
void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

改写插入删除 

有了删除指定位置节点函数和指定位置前插入函数,我们就可以改写尾插尾删、头插头删函数。

//尾插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead, x);
}
//尾删节点
void LPopBack(LNode* phead)
{
	LErase(phead->prev);
}
//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead->next, x);
}
//头删节点
void LPopFront(LNode* phead)
{
	LErase(phead->next);
}

判断链表是否为空

//判断链表是否为空
bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

计算链表长度

//计算链表长度
int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

销毁链表

//销毁链表
void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
    //将每个节点都释放
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动将phead置为空指针
	free(phead);
}

双链表完整代码

List.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <stdbool.h>

typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

//初始化双链表
LNode* LInit();

//打印双链表
void LPrint(LNode* phead);

//创建一个节点
LNode* BuyListNode(LDataType x);

//尾插尾删
void LPushBack(LNode* phead, LDataType x);
void LPopBack(LNode* phead);

//头插头删
void LPushFront(LNode* phead, LDataType x);
void LPopFront(LNode* phead);

//在指定位置前插入,删除指定位置
void LErase(LNode* pos);
void LInsert(LNode* pos, LDataType x);

//销毁链表
void LDestroy(LNode* phead);

//判断链表是否为空
bool LIsEmpty(LNode* phead);

//计算链表节点个数
size_t LSize(LNode* phead);

List.c

#include "List.h"


LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

void LPushBack(LNode* phead,LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* tail = phead->prev;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;

	//LInsert(phead, x);
}

void LPopBack(LNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;

	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
	//LErase(phead->prev);
}

void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;

	//LInsert(phead->next, x);
}

void LPopFront(LNode* phead)
{
	//assert(phead);
	//assert(phead->next != phead);
	//LNode* first = phead->next;
	//LNode* newfirst = first->next;

	//free(first);
	//phead->next = newfirst;
	//newfirst->prev = phead;

	LErase(phead->next);
}

LNode* LFind(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;

	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动置为空指针
	free(phead);
}

bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

浅谈链表及顺序表

链表和顺序表都作为线性表,他们之间有何异同呢?如以下表格:

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

以上就是双链表实现的全部内容了,希望能够帮助到大家。如果有不对的地方请各位大佬在评论区指正(鞠躬)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Speed and Memory Efficient Dense RGB-D SLAM in Dynamic Scenes论文笔记

Speed and Memory Efficient Dense RGB-D SLAM in Dynamic Scenes论文笔记 论文中的主要引用文献&#xff1a; [7:A coarse and relevant 3d representation for fast and lightweight rgb-d mapping] 超表元建图 [14: Fast optical flow using dense inverse search] 稠密光流…

后端Web开发框架(Java)

为什么使用Spring Boot 简化配置&#xff0c;无需编写太多的 xml 配置文件&#xff0c;效率很高&#xff1b;Spring 可以整合很多各式各样的框架&#xff0c;并能很好的集成&#xff1b;基于 Spring 构建&#xff0c;使开发者快速入门&#xff0c;门槛很低&#xff1b;Spring …

LabVIEW调用自己写的DLL

首先&#xff0c;我用的LabVIEW是8.5版本的&#xff0c;比较老但工作需要 先新建VI 程序框图中选择 互连接口 - 库与可执行程序 选择 调用库函数… 拖到面板 并右击它 选择配置 在库名或路径中选择写好的DLL方案中的DEBUG中dll文件 确定以后就要选择哪个函数&#xff0c;并…

【数字图像处理】毛笔字细化

源码链接&#xff1a;calligraphy.cpp 一、实验要求 附件是书法毛笔字&#xff0c;请将附件图片中“年少有为”四个字进行笔画细化。 二、实验内容 首先观察图片&#xff0c;是只将黑色的毛笔字部分进行细化&#xff0c;所以需要先把印章这类的区域去除。先通过将图片转到h…

Content Security Policy (CSP) 介绍

内容安全策略 (CSP) 是一个额外的安全层&#xff0c;用于检测并削弱某些特定类型的攻击&#xff0c;包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件&#xff0c;这些攻击都是主要的手段。 起因 当我不经意间在 Twitter 页面 view source…

JavaScript 简单类型与复杂类型

JavaScript 简单类型与复杂类型 目录JavaScript 简单类型与复杂类型1. 简单类型与复杂类型2. 堆和栈3. 简单类型的内存分配4.复杂类型的内存分配5. 简单类型传参6. 复杂类型传参7.下面是代码1、Math对象最大值2. 封装自己的数学对象3. Math绝对值和三个取整方法4.Math对象获取随…

WebSocket实现聊天室

需求 实现用户登录功能展示用户好友列表功能实现用户历史消息展示实现单聊信息和群聊信息 效果展示 用户登录 好友列表展示 历史消息展示 聊天 代码实现 说明&#xff1a;Springboot项目&#xff0c;页面是用 thymeleaf 整合的。 maven依赖 <dependencies><depen…

π122E31兼容ISO7221CD 200Mbps高速率 双通道数字隔离器

π122E31兼容ISO7221CD 200Mbps高速率 双通道数字隔离器&#xff0c;具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。 产品传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现 3.0kVrms 隔离耐压等级和 DC 到…

MySQL数据库的安装与实现

MySQL在win系统中的安装 第1步&#xff1a;下载安装&#xff08;在windows系统中安装&#xff09; http://downloads.mysql.com/archives/community/ 我选择安装的是5.7.31&#xff0c;一般MySQL主要分为两个版本&#xff0c;一个是5.7系列&#xff0c;一个是5.8系列&#xf…

Linux 音频驱动

1 I.MX6ULL 开发板通过此接口外接了一个 WM8960 音频 DAC 芯片。 2 在信号处理领域&#xff0c;外界的声音是模拟信号&#xff0c;处理器能理解的是数字信号&#xff0c;因此这里就涉及到一个模拟信号转换为数字信号的过程&#xff0c;而完成这个功能的就是 ADC 芯片。 如果处…

MySQL的锁

把那些可能会被多个线程同时操作的资源称为临界资源&#xff0c;加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。数据库作为用户共享的一个资源&#xff0c;如何保证数据并发访问一致性也是所有数据库必须解决的问题&#xff0c;如何加锁是数据库并发访问性能…

字节前端高频手写面试题(持续更新中)

Promise // 模拟实现Promise // Promise利用三大手段解决回调地狱&#xff1a; // 1. 回调函数延迟绑定 // 2. 返回值穿透 // 3. 错误冒泡// 定义三种状态 const PENDING PENDING; // 进行中 const FULFILLED FULFILLED; // 已成功 const REJECTED REJECTED; // 已…

Vite构建工具

什么是构建工具&#xff1a;打包:将我们写的浏览器不认识的代码交给构建工具进行编译处理的过程就叫做打包&#xff0c;打包完成以后会给我们一个浏览器可以认识的文件 一个构建工具他到底承担了哪些脏活累活: 1. 模块化开发支持:支持直接从node_modules里引入代码&#xff0b…

十三、Kubernetes yaml资源清单详解

1、概述 kubectl提供了各种命令&#xff0c;来管理集群中的pod&#xff0c;但是这些命令都是为了方便运维测试&#xff0c;实际生产部署还得用yaml文件来部署&#xff0c;所以弄清楚各类资源的字段是非常重要的。 资源清单就是k8s当中用来定义pod的文件&#xff0c;语法格式遵…

C#语言实例源码系列-实现ID卡的识别

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

Vlan的原理与配置

传统以太网的问题 规模大了&#xff0c;之后导致性能也很差&#xff0c;广播会增加 解决&#xff1a;用Vlan&#xff0c;不受地域限制&#xff0c;同一Vlan内的设备才能直接进行二层通信 实验 首先配置以上vlan与电脑ip 配置IP之后可以进行ping命令测试刚开始是连通的&#x…

计算机视觉实战----AlexNet网络及使用colab跑YoloV5代码

系列文章目录 文章目录系列文章目录前言一、用colab薅羊毛二、使用百度飞浆操作三、二、使用步骤1.引入库2.读入数据总结前言 一、用colab薅羊毛 Colaboratory 简称“Colab”&#xff0c;是 Google Research 团队开发的一款产品。在 Colab 中&#xff0c;任何人都可以通过浏览…

分享107个PHP源码,总有一款适合您

链接&#xff1a;https://pan.baidu.com/s/1Su77mBUx87vk0lzSLyvnyw?pwdyo96 提取码&#xff1a;yo96 PHP源码 分享107个PHP源码&#xff0c;总有一款适合您 page_count 1 # 每个栏目开始业务content"text/html; charsetgb2312"base_url "https://down.c…

基于 Spring Boot 的 RESTful API 设计与实现

RESTful 是一种规范&#xff0c;符合 RESTful 的 Api 就是 RESTful Api。简单的说就是可联网设备利用 HTTP 协议通过 GET、POST、DELETE、PUT、PATCH 来操作具有 URI 标识的服务器资源&#xff0c;返回统一格式的资源信息&#xff0c;包括 JSON、XML、CSV、ProtoBuf、其他格式。…

OpenVINS 官方文档 第一部分

参考链接&#xff1a;OpenVINS https://docs.openvins.com/index.html 1. Getting Started 欢迎来到OpenVIINS项目&#xff01;以下指南将帮助新用户下载软件并在我们支持的数据集上运行。此外&#xff0c;我们还提供有关如何在我们的系统上运行您自己的传感器的信息&#xff0…