【数据结构】带头双向链表的简单实现

news2024/11/24 17:40:16

目录

  • 前言
  • 链表的实现
    • List.h
    • List.c
      • **ListCreate()**
      • **LTInit()**
      • **ListPushBack()**
      • **ListPopBack()**
      • **ListPrint()**
      • **ListPushFront()**
      • **ListPopFront()**
      • **ListFind()**
      • **ListInsert()**
      • **ListErase()**
      • ListErase()
  • test.c

前言

该篇博客主要讲解了带头双向链表的实现和一些细节,相对于单链表,带头双向链表代码实现是更为简单的,而在物理结构上是更为复杂的。它可以帮助我们快速的对链表的头尾进行操作,单就链表而言,带头的双向链表无疑是其中最为实用的。
在这里插入图片描述

链表的实现

List.h

#pragma once
#include<stdio.h>
#include<assert.h>

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

ListNode* ListCreate();//创建返回链表的头节点
void LTInit(ListNode** phead);//初始化头节点

void ListDestroy(ListNode* plist);//双向链表销毁

void ListPrint(ListNode* plist);//双向链表打印

void ListPushBack(ListNode* plist, LTDataType x);//双向链表尾插

void ListPopBack(ListNode* plist);//双向链表尾删

void ListPushFront(ListNode* plist, LTDataType x);//双向链表头插

void ListPopFront(ListNode* plist);//双向链表头删

ListNode* ListFind(ListNode* plist, LTDataType x);//双向链表查找

void ListInsert(ListNode* pos, LTDataType x);//双向链表在pos的前面进行插入

void ListErase(ListNode* pos);//双向链表删除pos位置的结点

对各实现方法进行声明,使包含头文件后的各个文件都可以调用其他包含相同头文件的文件内定义的函数。
该链表的实现思路为:List.h声明,List.c实现各函数,test.c对各函数进行调用。

List.c

ListCreate()

功能:创建一个结点并返回

//创建返回链表的头节点
ListNode* ListCreate(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (!newNode)
	{
		perror("malloc fail:");
	}
	newNode->data = x;
	newNode->next = NULL;
	newNode->prev = NULL;

	return newNode;
}

LTInit()

功能:初始化头结点

//初始化头节点
void LTInit(ListNode** phead)
{
	*phead = ListCreate(-1);
	(*phead)->next = *phead;
	(*phead)->prev = *phead;
}

对头节点的操作有两种方法,
一:使用ListNode作为返回的类型,直接返回创建好的结点的地址。
二:使用二级指针,二级指针指向包含头结点的指针的地址,一次解引用后即为包含头节点的指针本身。
两种方法各有所长,使用第一种可以更简单一些,但是实现的时候页面会不美观,使用第二种在不熟悉的情况下容易出现错误。

ListPushBack()

功能:该函数进行尾插操作
在这里插入图片描述

//双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	assert(plist);

	ListNode* Node = ListCreate(x);
	Node->next = plist;
	Node->prev = plist->prev;
	plist->prev->next = Node;
	plist->prev = Node;
}

首先判断是否进行断言(判断所给参数是否为空),判断是否需要断言的依据为所给参数是否一定不为空,若一定不为空,则需要断言,防止传入空指针,该函数的主要功能为对链表进行尾插,传入的参数为指向头结点的指针,若为空则该链表不存在,所以所传的参数一定不为空,需要进行断言。

该操作必须先对新的结点进行操作,使它指向它的尾结点和头结点后在对它的相邻结点进行操作,如果直接对相邻结点进行操作,将头节点指向的上一个指针改为新节点,在想将尾结点指向的下一个结点指向新结点就无法轻易做到,必须要先对链表进行遍历后找到尾结点才能操作。

ListPopBack()

功能:对尾结点进行删除
在这里插入图片描述

//双向链表尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	ListNode* temp = plist->prev;
	if (temp != plist)
	{
		temp->prev->next = temp->next;
		plist->prev = temp->prev;
		free(temp);
		temp = NULL;
	}
	else
		printf("该链表只有头节点,无法删除。");
}

首先判断是否需要断言(assert:判断所给参数是否为空),该函数的主要任务是进行尾删操作,若链表为空,无法进行该操作,所以链表一定不为空。
将尾结点前的那个结点与头结点相连,利用一个临时的结点存储尾结点的地址,完成删除后,对临时结点进行释放,将空间还给内存。

ListPrint()

功能:打印链表

//双向链表打印
void ListPrint(ListNode* plist)
{
	assert(plist);
	ListNode* cur = plist->next;

	while (cur != plist)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

从头节点后的第一个结点开始打印、遍历,知道遇到头节点,打印停止。

ListPushFront()

功能:进行双向链表的头插
在这里插入图片描述

//双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* Node = ListCreate(x);
	Node->prev = plist;
	Node->next = plist->next;
	plist->next->prev = Node;
	plist->next = Node;
}

首结点不能为空,需要进行断言。
双向链表头插与尾插做法相同。

ListPopFront()

功能:对链表头结点后的第一个结点进行删除操作
在这里插入图片描述

//双向链表头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	ListNode* temp = plist->next;
	if (temp != plist)
	{
		plist->next = temp->next;
		temp->next->prev = plist;
		free(temp);
		temp = NULL;
	}
	else
		printf("该双向链表只有头节点,无法删除!/n");
}

对链表进行删除结点操作,该链表一定不为空,需要进行断言。
先用一个指针接收头节点下的第一个结点查看是否为头节点,若是为头节点表示该链表没有数据,直接退出。
否则,将头节点指向的下一个结点改为临时结点的下一个结点,将连续结点的下一个结点指向的上一个结点改为头结点,删除临时结点即可。

ListFind()

功能:查找链表中存储元素为x的第一个结点,并返回该结点

//双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListNode* cur = plist->next;

	while (cur != plist)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

链表不能为空,否则查找结点无意义。
使用临时变量cur接收头节点下第一个结点的地址,进行循环判断,当遇到x时,返回该结点的地址,当循环完毕,cur指向头节点的位置时,表示已经遍历了一遍,没有找到给元素所存储的结点,返回NULL

ListInsert()

功能:在pos结点前插入存储元素为x的新结点。
在这里插入图片描述

//双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* Node = ListCreate(x);
	Node->next = pos;
	Node->prev = pos->prev;
	pos->prev->next = Node;
	pos->prev = Node;
}

pos参数若是空将毫无意义,必须要断言
该链表为双向链表,可以根据给出的一个结点找到他的上一个结点和下一个结点,所以我们对pos指针进行操作即可。
如果首先对pos指针进行操作,那它的上一个结点将无法找到,应该使新建的新结点分别对pos结点和它的上一个节点进行操作后,断开pos结点和它上一个结点的链接即可。
当我们有了这个操作后,那我们可以改动链表的首插和尾插

//尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListInsert(plist->prev,x);
}

//首插
void ListPushFront(ListNode* plist, LTDataType x)
{
	assert(plist);
	ListInsert(plist->next,x);
}

ListErase()

功能:删除双向链表中给出的参数结点。
在这里插入图片描述

//双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* Node = pos->prev;
	Node->next = pos->next;
	pos->next->prev = Node;
	free(pos);
	pos = NULL;
}

同上可得,pos结点一定不为空。
使用两个局部变量分别接收pos结点指向的上一个结点和下一个结点,方便进行操作,不使用其他变量直接删除也可。这里使用一个变量Node接收上一个结点操作。将Node结点指向的下一个结点给为pos指向的下一个结点,pos指向的下一个结点指向的上一个节点指向Node,最后释放pos结点,完成删除操作。
当我们完成这个函数后,我们可以将链表的首删和尾删可以修改的更加简单

//尾删
void ListPopBack(ListNode* plist)
{
	assert(plist);
	ListErase(plist->prev);
}

//头删
void ListPopFront(ListNode* plist)
{
	assert(plist);
	ListErase(plist->next);
}

ListErase()

功能:对创建的链表进行销毁操作

//双向链表销毁
void ListDestroy(ListNode** plist)
{
	assert(*plist);

	ListNode* cur = (*plist)->next;
	while (cur != (*plist))
	{
		ListNode* temp = cur->next;
		free(cur);
		cur = temp;
	}
	free(*plist);
	*plist = NULL;
}

若链表为空,销毁操作无意义,必须断言。
链表的销毁要对链表的头节点进行操作,若是传递的是头节点的地址,在VS中对该地址进行释放后链表中的数据变为随机值,但无法对其进行置空,打印头节点依然可以打印出数据,如果传递存储头结点的指针的地址,对其进行操作,即可解决这个问题,对头结点进行操作必须使用二级指针。
从链表头结点下的第一个结点开始,进行释放操作,最后对头节点进行释放和置空即可完成对链表销毁的操作。

test.c

对各个接口进行实现,这里我简单的测试了一下,并没有写成菜单的形式,若是感兴趣可以自行改写

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

void text(ListNode* phead)
{
	LTInit(&phead);

	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPushBack(phead, 5);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListPushFront(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	//ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPrint(phead);

	ListNode* pos = ListFind(phead, 3);
	if (pos)
	{
		printf("[%d|%p]\n", pos->data, pos);
		ListInsert(pos, 8);
		ListErase(pos);
		ListPrint(phead);
	}
}

int main()
{
	ListNode phead;
	text(&phead);

	return 0;
}

在这里插入图片描述

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

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

相关文章

Cadence Allegro DXF结构图的导入详细教程

很多消费类板卡的结构都是异形的&#xff0c;由专业的CAD结构工程师对其进行精准的设计&#xff0c;PCB布线工程师可以根据结构工程师提供的2D图&#xff08;DWG或DXF格式&#xff09;进行精准的导入操作&#xff0c;在PCB中定义板型结构。 同时&#xff0c;对于一些工控板或者…

Ajax--跨域与JSONP--案例-淘宝搜索

要实现的UI效果 获取用户输入的搜索关键词 为了获取到用户每次按下键盘输入的内容&#xff0c;需要监听输入框的 keyup 事件&#xff0c;示例代码如下&#xff1a; // 监听文本框的 keyup 事件$(#ipt).on(keyup, function() {// 获取用户输入的内容var keywords $(this).val…

支撑向量机

1、支持向量机算法原理 支持向量机&#xff08;Support Vetor Machine&#xff0c;SVM&#xff09;由Vapnik等人于1995年首先提出&#xff0c;在解决小样本、非线性及高维模式识别中表现出许多特有的优势&#xff0c;并推广到人脸识别、行人检测和文本分类等其他机器学习问题中…

HTML期末作业:基于html+css+javascript+jquery实现古诗词网页 学生网页设计作品 web前端开发技术 web课程设计 网页规划与设计

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

初学C语言有什么建议?

什么&#xff1f;开玩笑&#xff0c;新手学C语言&#xff1f; 确实新手不学C语言学什么呢&#xff1f;为什么这么推荐新手学C语言呢具体看看下面的解释吧&#xff1f; C的重要性 我总结了网上很多人的说法如下&#xff1a; C语言是计算机界公认的有史以来最重要的语言。C语…

R语言偏相关和典型相关

本文首发于公众号&#xff1a;医学和生信笔记&#xff0c;完美观看体验请至公众号查看本文。 文章目录偏相关&#xff08;partial correlation&#xff09;偏相关散点图典型相关&#xff08;Canonical Correlation&#xff09;使用R语言实现偏相关分析和典型相关分析&#xff0…

一个对C#程序混淆加密,小巧但够用的小工具

对于我们程序员来说&#xff0c;平常开发的桌面应用程序&#xff0c;如果不进行一定程度的加密、混淆&#xff0c;是很容易通过反编译手段进行破解的&#xff0c;特别是一些商业用途的C#软件&#xff0c;更是容易被破解。 所以今天给大家推荐一个对C#程序加密混淆项目&#xf…

脱离CRUD苦海 !性能优化全栈小册来了!

性能优化 随着互联网的高速发展&#xff0c;互联网行业已经从IT时代慢慢步入到DT时代。对于Java程序员的要求越来越高&#xff0c;只是单纯的掌握CRUD以不足以胜任互联网公司的相关职位&#xff0c;大量招聘岗位显示&#xff1a;如果是面试中高级的Java岗&#xff0c;基本上都…

flex1时内容溢出

目标效果&#xff1a;右边黄色部分填充减去红色部分的剩余部分 原理: flex: 1 代码&#xff1a; <div class"box"><div class"inner-left"></div><div class"inner-right"><span class"inner-right-content&…

RK3568平台开发系列讲解(NPU篇)让 NPU 跑起来

🚀返回专栏总目录 文章目录 一、在 Android 系统中使用 NPU1.1、下载编译所需工具1.2、修改编译工具路径1.3、更新 RKNN 模型1.4、编译 demo沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍如何让NPU跑起来。 一、在 Android 系统中使用 NPU 下载 rknpu2 …

Hadoop的eclipse搭建(客观莫划走,留下来看一眼(适用人群学生初学,其他人看看就行))

前言&#xff1a;Hadoop的eclipse搭建是建立在Hadoop的安装之后进行的&#xff0c;因为Linux上的Hadoop和Windows上的Hadoop版本要求一致&#xff0c;不一致可能会出现某些问题 准备工作&#xff1a;Java的安装包、eclipse的安装包、Hadoop的包&#xff08;Windows的Hadoop安装…

基于Socket编程下 实现Linux-Linux、Linux-Windows udp通信

文章目录一、通信实现二、Linux-Linux1. 服务器 Server2. 客户端 Client三、Linux-Windows1. 服务器 Linux_Server2. 客户端 Windows_Client程序源码一、通信实现 1. Linux-Linux 在虚拟机下开启俩个终端&#xff0c;分别运行服务器和客户端程序(服务器运行在前&#xff0c;客…

栈的简单实现及应用

栈的简单实现及其应用什么是栈&#xff1f;栈的分类栈的数据结构栈的基本操作栈的初始化栈的销毁入栈操作出栈和栈空的判断获取栈顶元素获取栈的元素个数头文件总结栈的应用什么是栈&#xff1f; 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除…

【毕业设计】垃圾邮件(短信)分类算法研究与实现 - 机器学习

文章目录1 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后1 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#…

Vue面试题-答案、例子

1、Vue的生命周期 每一个vue实例从创建到销毁的过程&#xff0c;就是这个vue实例的生命周期。在这个过程中&#xff0c;他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。 将要创建 >调用beforeCreate函数 创建完毕 >调用creat…

振弦采集模块复位( 重启)及恢复出厂设置

振弦采集模块复位&#xff08; 重启&#xff09;及恢复出厂设置 以下几种情况&#xff08;或操作&#xff09;可使模块产生复位动作&#xff0c;重新启动。 &#xff08; 1&#xff09; 在模块正常工作期间&#xff0c;向寄存器 SYS_FUN 发送软复位指令 0x01&#xff1b; &…

74ls192无法正常使用。

分析与解决74ls192芯片无法在proteus中正常运行 博主最近要做电子技术课程设计&#xff0c;于是重新拾起了长久不用的proteus。在构建倒计时电路时&#xff0c;发现了一个问题&#xff1a; 74ls192芯片&#xff0c;在软件提供的时钟信号下能正常开启计时。但是在自己使用的55…

从理论到实践:MySQL性能优化和高可用架构,一次讲清

数据库系统作为IT业务系统的核心&#xff0c;其高可用性和容灾能力对整个业务系统的连续性和数据完整性起着至关重要的作用&#xff0c;是企业正常运营的基石 尤其是在性能优化与高可用架构两方面&#xff0c;很多从业多年的DBA限于生产环境的固定体系&#xff0c;往往盲人摸象…

Grafana-web使用说明

本文分别记录了&#xff1a; Grafana使用步骤P50 P99 min max m1_rate等指标分别是什么意思&#xff0c;Metrics为何不会对“吞吐量”指标记录P99 min 等聚合Metrics常用的几种记录方式&#xff08;我司用了两种&#xff09; 1.场景 Metrics收集日志交给Graphite&#xff08;…

第九节:类和对象【三】【static、代码块、对象的打印】

目录 &#x1f947;1.什么是封装 &#x1f4d8;1.1封装的实现 &#x1f392;2.static成员 &#x1f4d2;2.1 再谈学生类 ​编辑 &#x1f4d7;2.2 static修饰成员变量 2.3 static修饰成员方法 &#x1f4d5;2.4 static成员变量初始化 &#x1f532;3. 代码块 &#x…