【数据结构】手撕单链表

news2024/11/25 11:00:10

目录

一,链表的概念及结构

二,接口实现

        1,单链表的创建

        2,接口函数

        3,动态创立新结点

        4,打印

        5,头插

        6,头删

        7,尾插

        8,尾删

        9,查找

        10,单链表在pos位置之后插入x

        11,单链表删除pos位置之后的值

        12,销毁

三,源代码

LKList.h

LKList.c

四,总结


一,链表的概念及结构

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

 

 

 而在数据结构中:

注意:

1,从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

 2,现实中的结点一般都是从堆上申请出来的

 3,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续;

 实际中链表的结构非常多样,今天我们来写一下单链表,此表一会其他的自然水到渠成!

二,接口实现

        1,单链表的创建

//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{
	LKLDataType data;
	struct LinKedListNode* next;
}LKLNode;

首先创建一个结构体表示单链表data是存储的值,LKLDataType是储存的值的数据类型,next是结点----指向下一个;

这里的LKLDataTypeint的重命名,也可以说是数据类型的重命名,这样统一化方便后续更改;

        2,接口函数

// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);

 这是以上要实现的接口函数;

        3,动态创立新结点

//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{
	LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

后面创立新节点时直接调用此函数,一定要向堆区申请空间,这样函数结束空间会保留不会被回收;

        4,打印

//打印
void LKLPrint(LKLNode* phead)
{
	assert(phead);
	LKLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

打印也就是打印data的值,用cur=phead然后每次打印完都让cur走向下一个直到为空结束;

        5,头插

//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	LKLNode* newnode = BuyLKLNode(x);
	newnode->next = *phead;
	*phead = newnode;
}

先断言一下,既然插入数据那就要申请一个新节点,然后令新节点的next指向phead然后再令phead指向新节点;

        6,头删

//头删
void LKLNodeBackFront(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//非空
	LKLNode* newnode = (*phead)->next;
	free(*phead);
	*phead = newnode;
}

还是先断言,有人会问为什么要断言两次?其实很好判断,哪个需要解引用那个就需要断言;

令一个变量newnode等于头结点的下一个,在释放头结点,在令头结点指向newnode即可;

        7,尾插

//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	assert(*phead);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = *phead;
	//为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//非空
	else
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

还是先断言判断,然后要插入一个新的数据先申请一个新结点,如果头结点为空则直接让头结点指向新结点即可,如果头结点不为空,则需要找到next为空的结点,这里用一个循环搞定,然后再直接让next为空的结点指向新节点即可;

        8,尾删

//尾删
void LKLNodePopBack(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//一个
	if ((*phead)->next==NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//两个及以上
	else
	{
		LKLNode* tail = *phead;

		/*LKLNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(prev->next);
		prev->next = NULL;*/

		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

还是先断言一下,然后这里有两种情况链表只有一个结点,两个以上结点的时候

当链表只有一个结点也就是头结点,直接头删即可;

两个以上结点的时候,我这里有两种解决方案;

方案一常规法:先用循环找到next为空的结点,并且在循环里保留上一个结点prev,然后释放next为空的结点再让prevnext指向空即可;

方案二:不需要标记上一个结点,直接原地判断,判断结点的nextnext是否为空,否则继续向后推进,是则释放结点的next然后再令自己的next指向空也就相当于变成了尾结点

        9,查找

// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{
	assert(phead);
	LKLNode* pos = phead;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}

老样子先断言一下,然后直接用循环遍历链表找到datax的值然后返回此结点即可;

        10,单链表在pos位置之后插入x

// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{
	assert(pos);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = pos;
	newnode->next = cur->next;
	cur->next = newnode;
}

先断言,要插入数据先申请一个新结点,然后令新结点的next指向posnext,再返回来让posnext指向新结点;

        11,单链表删除pos位置之后的值

// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{
	assert(pos);
	assert(pos->next);
	LKLNode* cur = pos;
	LKLNode* newnode = cur->next->next;
	free(cur->next);
	cur->next = newnode;
}

要删除值首先要确保得有值,所以开始断言;

先定义一个变量newnode指向posnextnext,然后再释放posnext,再令pos指向newnode以达到删除之后的效果;

        12,销毁

// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{
	assert(phead);
	assert(*phead);
	LKLNode* cur = *phead;
	LKLNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}

老样子那个需要解引用那个就先断言一下,然后用循环遍历,先标记下一个结点,然后释放自己,再让自己指向标记的结点直到为空结束;

三,源代码

LKList.h

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

//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{
	LKLDataType data;
	struct LinKedListNode* next;
}LKLNode;

// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);

LKList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"LKList.h"

//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{
	LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	LKLNode* newnode = BuyLKLNode(x);
	newnode->next = *phead;
	*phead = newnode;
}
//头删
void LKLNodeBackFront(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//非空
	LKLNode* newnode = (*phead)->next;
	free(*phead);
	*phead = newnode;
}
//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	assert(*phead);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = *phead;
	//为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//非空
	else
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
//尾删
void LKLNodePopBack(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//一个
	if ((*phead)->next==NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//两个及以上
	else
	{
		LKLNode* tail = *phead;

		/*LKLNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(prev->next);
		prev->next = NULL;*/

		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{
	assert(phead);
	LKLNode* pos = phead;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{
	assert(pos);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = pos;
	newnode->next = cur->next;
	cur->next = newnode;
}
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{
	assert(pos);
	assert(pos->next);
	LKLNode* cur = pos;
	LKLNode* newnode = cur->next->next;
	free(cur->next);
	cur->next = newnode;
}

// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{
	assert(phead);
	assert(*phead);
	LKLNode* cur = *phead;
	LKLNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}
//打印
void LKLPrint(LKLNode* phead)
{
	assert(phead);
	LKLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

四,总结

做数据结构的题目画图很重要,小伙伴们刚开始喜欢用脑子去构图想象,但遇到复杂的情况会紊乱的,画图最为可观方便,可以培养一个良好的画图习惯;

如有不足之处欢迎来补充交流!

完结。。。


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

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

相关文章

P5-P8都需要掌握哪些技术

P5级别技术栈 职级:中级工程师 薪资:年薪20万-40万 要求:扎实的Java基础&#xff0c;对常见的设计模式与数据结构算法有颇多研究&#xff0c;熟悉常见的开发规范。 P6级别技术栈 职级:高级/资深工程师 薪资:年薪40万-60万 要求:熟悉各种技术中间件的使用与优化&#…

QWidget的ui界面绘制成图片

文章目录 源文件源码解释效果修复图片清晰度 源文件 #include "widget.h" #include "ui_widget.h"#include <QPixmap> #include <QDir>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 构造…

[Linux]进程程序替换

[Linux]进程程序替换 文章目录 [Linux]进程程序替换进程程序替换的意义见一见进程程序替换进程程序替换的原理进程程序替换中的写时拷贝介绍进程程序替换接口 进程程序替换的意义 Linux系统下使用fork系统函数创建子进程后&#xff0c;子进程只能执行继承的部分父进程代码&…

OB Cloud:如何为用户提供可持续的降本增效?

易鸿伟 OceanBase资深研发总监 2014年加入蚂蚁集团&#xff0c;深度参与了蚂蚁全站单元化架构、云架构、大促、网商银行的等几乎所有重点架构的升级与建设&#xff0c;主导集团内第四代微服务框架的重构&#xff0c;第五代数据访问层的重构&#xff0c;目前主要负责云数据库相…

解释基本的3D理论

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 坐标系 3D 本质上是关于 3D 空间中形状的表示&#xff0c;并使用坐标系来计算它们的位置。 WebGL 使用右侧坐标系 — 轴指向右侧&#xff0c;轴指向上方&#xff0c;轴指向屏幕外&#xff0c;如上图所示。xyz 对象 …

数据可视化与数字孪生:理解两者的区别

在数字化时代&#xff0c;数据技术正在引领创新&#xff0c;其中数据可视化和数字孪生是两个备受关注的概念。尽管它们都涉及数据的应用&#xff0c;但在本质和应用方面存在显著区别。本文带大探讨数据可视化与数字孪生的差异。 概念 数据可视化&#xff1a; 数据可视化是将复…

宿舍固定资产怎么管理

宿舍固定资产的管理需要做到以下几点&#xff1a; 固定资产购置&#xff1a;宿舍的固定资产包括设备、家具、厨房用品等&#xff0c;购置时需要注意质量和价格&#xff0c;并进行登记。 固定资产登记&#xff1a;将宿舍的固定资产名称、型号、规格、数量、单价、金额、…

淘宝API接口:提高电商运营效率与用户体验的利器(淘宝API接口使用指南)

淘宝API接口&#xff1a;提高电商运营效率与用户体验的利器 随着电商行业的快速发展&#xff0c;淘宝作为国内最大的电商平台之一&#xff0c;不断探索和创新&#xff0c;以满足不断变化的用户需求和商家需求。其中&#xff0c;淘宝API接口便是其创新的一个重要方面。本文将深…

ReactPy:使用 Python 构建动态前端应用程序

在 Web 开发领域,ReactJS 已成为主导者,为开发人员提供了用于创建动态和交互式用户界面的强大工具集。但是,如果您更喜欢 Python 的多功能性和简单性作为后端,并且希望在前端也利用它的功能,该怎么办?ReactPy 是一个 Python 库,它将熟悉的 ReactJS 语法和灵活性带入了 P…

通过类定义一个网络

import torch from torch import nnx torch.ones(2,10)class MLP(nn.Module):def __init__(self):super().__init__()self.out nn.Linear(10, 1)def forward(self,x):return self.out(x) 1. 代码解析 如何定义一个类&#xff1f;self 又是什么东西&#xff1f;类是如何继承基…

高尔夫APP外包开发主要功能

高尔夫小程序可以实现教练预约、场地预地、训练课程、积分系统、社交功能等&#xff0c;通过小程序方便用户&#xff0c;同时也提球场的管理能力。今天和大家分享一些主要功能和注意的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包…

详细说明OSPF常见的LSA

目录 1类LSA &#xff08;Router LSA&#xff09;介绍 总结&#xff1a;1类LSA 2类LSA &#xff08;Network LSA&#xff09;介绍 总结&#xff1a;2类LSA 3类LSA &#xff08;Summary LSA&#xff09;介绍 总结&#xff1a;3类LSA 5类LSA &#xff08;ase LSA&…

二肽-2——祛除眼部水肿和眼部黑眼圈

简介 眼袋形成的一个重要的原因是水肿, 诱因主要是淋巴循环减弱和毛细血管的通透性增加。 INCI 名称 二肽-2 多肽序列 VW CAS号 24587-37-9 机理 抑制血管紧张素转换酶&#xff0c;增强眼部淋巴循环&#xff0c;促进水分排出 二肽-2是一种二胜肽&#xff0c;带有二种标…

高忆管理:沪指弱势调整跌0.53%,地产板块走弱,光刻机概念拉升

31日早盘&#xff0c;A股两市弱势调整。截至午间收盘&#xff0c;沪指跌0.53%报3120.39点&#xff0c;深成指跌0.55%&#xff0c;创业板指跌0.54%&#xff0c;两市算计成交5291亿元。北向资金净流出36亿元。盘面上&#xff0c;半导体、中成药、黄金等板块走强&#xff0c;地产、…

生成式人工智能能否使数字孪生在能源和公用事业行业成为现实?

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 克服障碍&#xff0c;优化数字孪生优势 要实现数字孪生的优势&#xff0c;您需要数据和逻辑集成层以及基于角色的演示。如图 1 所示&#xff0c;在任何资产密集型行业&#xff08;如能源和公用事业&#xff09;中&…

高忆管理:A股上市券商“中考”成绩放榜,最大黑马是它

A股上市券商2023年半年报发表8月30日晚正式收官。全体上看&#xff0c;43家券商中有10家营收超百亿元&#xff0c;多达30家完成了营收及净利润的双增。头部券商中&#xff0c;我国银河近年来运营成绩排名稳步提高&#xff1b;区域性券商中&#xff0c;天风证券成最大黑马&#…

iOS逆向进阶:iOS进程间通信方案深入探究与local socket介绍

在移动应用开发中&#xff0c;进程间通信&#xff08;Inter-Process Communication&#xff0c;IPC&#xff09;是一项至关重要的技术&#xff0c;用于不同应用之间的协作和数据共享。在iOS生态系统中&#xff0c;进程和线程是基本的概念&#xff0c;而进程间通信方案则为应用的…

行政固定资产应该怎么管理

行政需要管理的固定资产主要包括办公设备、交通工具、通讯设备、家具等。具体来说&#xff0c;行政需要管理的固定资产包括但不限于&#xff1a;电脑、打印机、传真机、复印机、投影仪、电话、传真机、传真纸、电话线、路由器、交换机、服务器、UPS电源、办公桌椅、沙发等。 行…

Java小项目【图书馆系统】

一、设计图书馆系统 Java是一个面向对象的语言&#xff0c;在编写代码的之前&#xff0c;我们要先确定有哪些对象 图书馆&#xff0c;首先有很多书&#xff0c;还有书架来放置这些书。然后是对书进行操作的人&#xff0c;比如普通用户和管理员。最后是对关于书的各种操作&#…

如何检测勒索软件攻击

什么是勒索软件 勒索软件又称勒索病毒&#xff0c;是一种特殊的恶意软件&#xff0c;又被归类为“阻断访问式攻击”&#xff08;denial-of-access attack&#xff09;&#xff0c;与其他病毒最大的不同在于攻击方法以及中毒方式。 攻击方法&#xff1a;攻击它采用技术手段限制…