【数据结构】C语言实现链表(单链表部分)

news2024/10/6 22:21:26

目录

前言

链表

链表的分类

1.单向或者双向

 2.带头或者不带头

 3.循环或者非循环

单链表实现

定义节点

接口函数实现

创建节点

打印链表

尾插节点

尾删节点

头插节点

 头删节点

 单链表查找

删除指定位置后的节点

 指定位置后插入节点

删除指定位置

指定位置插入节点

销毁链表

  单链表完整代码


前言

之前我们实现过顺序表,对于顺序表,我们也得思考下面几个问题:

1.中间/头部的插入删除,时间复杂度为O(N)。
2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看。

链表

概念及结构

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

相对于顺序表,链表在物理结构上并不连续,但是在逻辑结构上是连续的。如图:

 

注意:

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

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

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

链表的分类

实际中链表的结构非常多样,以下情况组合起来一共有8种链表结构:

1.单向或者双向

 2.带头或者不带头

 3.循环或者非循环

 虽然有这么多的链表的结构,但是实际中最常用的还是两种结构:

 1.无头单向非循环链表:结构简单,一般不会用来单独存储数据。实际中更多地是用来作为其他数据据结构的子结构,如哈希桶,图的邻接表等。另外这种结构在笔试面试中出现很多。

2.带头双向循环链表:结构最复杂,一般用来单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这种链表结构虽然复杂,使用代码实现起来会发现这种结构会有许多优势,实现起来反而更容易。

单链表实现

定义节点

链表的单个节点由数据域跟指针域构成,节点中指针域所存储的指针就是下一个节点的地址。

这里有一点需要注意,next的指针类型是struct SListNode*,不是SLTNode*

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType x;
	struct SListNode* next;
}SLTNode;

接口函数实现

PS:

        实现增删查改函数之前我们需要知道,单链表并不需要初始化函数,我们只需要创建一个struct SListNode* plist = NULL;由plist指针来管理我们的单链表就可以了。

链表作为数据结构的一种,要实现的功能无非就是增删查改,在实现这些功能之前,我们先来实现一个创建节点的函数。

创建节点

//创建节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    //创建节点失败就退出程序
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}

 有了创建节点函数之后,我们在实现一个打印链表信息的函数,这样我们在实现其他函数的时候可以通过打印链表来观察函数功能是否正确。

打印链表

//打印链表
void SLTPrint(SLTNode* phead)
{
	if (phead == NULL)
	{
		printf("NULL\n");
		return;
	}
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("NULL\n");
}

如果我们的链表是1 2 3 4 5;打印出来的结果就是1->2->3->4->5->NULL

尾插节点

//尾插
void SLT_PushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
    //如果链表为空,那么此时就是第一次向链表中插入节点,
    //那么只需要将我们创建的节点的指针赋值给*pphead就可以了
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
    //如果链表不为空,我们这时需要找到该链表的最后一个节点,
    //然后将我们创建的节点链接在最后一个节点的后面。
	SLTNode* cur = *pphead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

 

 

为什么传入的参数是二级指针?       

       在前面我们讲过,单链表的初始化非常简单,只需要我们创建一个struct SListNode* plist,就可以了。但是,在添加新节点的函数中,我们的参数为什么是一个二级指针呢?那是因为,我们实现的函数中,函数中的参数只是实参的一份拷贝,当我们想要改变他的时候,就必须要传入它的指针(地址)。当我们的链表没有节点的时候,此时plist就是一个NULL,我们向链表中添加节点后,之前的空链表就变成了一个具有一个节点的链表,那么这时就要改变plist。所以我们要传入plist的指针(地址),这样才能改变plist。

       说得简单一点就是,当传入一个空链表并且我们要向这个链表中添加新节点的时候,plist自身会发生改变,所以我们要传入plist的指针。

       后面的尾删、头插、头删函数传入的都是二级指针。都是因为这个原因。

尾删节点

//尾删节点
void SLT_PopBack(SLTNode** pphead)
{
    //如果传入的是空链表,那么就没有删除节点的必要
	assert(*pphead);
    //如果链表只有一个节点,那么
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
    //在删除最后一个节点的同时,我们需要找到最后一个节点的前一个节点
    //将它的next置成NULL
	SLTNode* cur = *pphead;
	SLTNode* prev = *pphead;
	while (cur->next != NULL)
	{
		prev = cur;
		cur = cur->next;
	}
    //记得将最后一个节点释放,虽然删除后我们也拿不到这个节点的地址
    //但是,这个节点是由我们手动开辟的,一定要置空,防止内存泄漏
	free(cur);
	prev->next = NULL;
}

 

头插节点

//头插节点
void SLT_PushFront(SLTNode** pphead, SLTDataType x)
{    
    //如果链表为空,直接将创建的节点赋值给*pphead
	if (*pphead == NULL)
	{
		*pphead = BuySLTNode(x);
		return;
	}
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;
}

 头删节点

//头删节点
void SLT_PopFront(SLTNode** pphead)
{
    //如果传入的是空链表,就没有必要删除
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* next = (*pphead)->next;
	free(cur);
    //将第一个节点删除之后,头节点改变。
	*pphead = next;
}

 

 单链表查找

//单链表查找节点
SLTNode* SLT_Find(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
        //找到就返回该节点指针
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
    //找不到就返回空指针
	return NULL;
}

删除指定位置后的节点

//删除指定位置后节点
void SLT_EraseAfter(SLTNode* pos)
{
    //如果pos是最后一个节点,那么就没有必要删除
	assert(pos->next);
	SLTNode* nextnode = pos->next->next;
    //动态开辟的节点,一定要释放
	free(pos->next);
	pos->next = nextnode;
}

 指定位置后插入节点

//指定位置后插入节点
void SLT_InsertAfter(SLTNode* pos, SLTDataType x)
{
    //断言,防止传入空指针
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

 

删除指定位置

//删除指定位置
void SLT_Erase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
    //如果删除的节点是第一个节点,那么就是头删
	if (*pphead == pos)
	{
		SLTNode* cur = *pphead;
		*pphead = (*pphead)->next;
		free(cur);
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}
	prev->next = cur->next;
	free(cur);
}

 

 

指定位置插入节点

//pos位置插入节点
void SLT_Insert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
    //如果pos是第一个节点,那么就是头插
	if (*pphead == pos)
	{
		SLTNode* newnode = BuySLTNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur!=pos)
	{
		prev = cur;
		cur = cur->next;
	}
	SLTNode* newnode = BuySLTNode(x);
	prev->next = newnode;
	newnode->next = pos;
}

销毁链表

//销毁链表
void SLT_Destroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* nextnode = *pphead;
	while (cur)
	{
		nextnode = cur->next;
		free(cur);
		cur = nextnode;
	}
    //最后将*pphead置空,防止非法访问
	*pphead = NULL;
}

  单链表完整代码

SList.h

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

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType x;
	struct SListNode* next;
}SLTNode;

//创建一个节点
SLTNode* BuySLTNode(SLTDataType x);

//打印链表
void SLTPrint(SLTNode* phead);

//尾删尾插
void SLT_PushBack(SLTNode** pphead, SLTDataType x);
void SLT_PopBack(SLTNode** pphead);

//头插头删
void SLT_PushFront(SLTNode** pphead, SLTDataType x);
void SLT_PopFront(SLTNode** pphead);

//单链表查找
SLTNode* SLT_Find(SLTNode* plist, SLTDataType x);

// 单链表在pos位置之后插入x
void SLT_InsertAfter(SLTNode * pos, SLTDataType x);

//在pos位置插入x
void SLT_Insert(SLTNode* phead, SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之后的值
void SLT_EraseAfter(SLTNode* pos);

//删除pos位置
void SLT_Erase(SLTNode** pphead, SLTNode* pos);

//销毁链表
void SLT_Destroy(SLTNode** pphead);

SList.c

#include "SList.h"

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPrint(SLTNode* phead)
{
	if (phead == NULL)
	{
		printf("NULL\n");
		return;
	}
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->x);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SLT_PushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

void SLT_PopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = *pphead;
	while (cur->next != NULL)
	{
		prev = cur;
		cur = cur->next;
	}
	free(cur);
	prev->next = NULL;
}

void SLT_PushFront(SLTNode** pphead, SLTDataType x)
{
	if (*pphead == NULL)
	{
		*pphead = BuySLTNode(x);
		return;
	}
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* cur = *pphead;
	newnode->next = cur;
	*pphead = newnode;
}

void SLT_PopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	SLTNode* next = (*pphead)->next;
	free(cur);
	*pphead = next;
}

SLTNode* SLT_Find(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->x == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return cur;
}

void SLT_InsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

void SLT_Insert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	if (*pphead == pos)
	{
		SLTNode* newnode = BuySLTNode(x);
		newnode->next = *pphead;
		*pphead = newnode;
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur!=pos)
	{
		prev = cur;
		cur = cur->next;
	}
	SLTNode* newnode = BuySLTNode(x);
	prev->next = newnode;
	newnode->next = pos;
}

void SLT_EraseAfter(SLTNode* pos)
{
	assert(pos->next);
	SLTNode* newnode = pos->next->next;
	free(pos->next);
	pos->next = newnode;
}

void SLT_Erase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SLTNode* cur = *pphead;
		*pphead = (*pphead)->next;
		free(cur);
		return;
	}
	SLTNode* cur = *pphead;
	SLTNode* prev = NULL;
	while (cur != pos)
	{
		prev = cur;
		cur = cur->next;
	}
	prev->next = cur->next;
	free(cur);
}

void SLT_Destroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLTNode* cur = *pphead;
	SLTNode* nextnode = *pphead;
	while (cur)
	{
		nextnode = cur->next;
		free(cur);
		cur = nextnode;
	}
	*pphead = NULL;
}

以上就是单链表实现的全部内容了,希望能够帮到大家。如果有什么错误的地方还请各位指正。

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

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

相关文章

Linux-7 文本编辑vivim

Linux-7 文本编辑vi/vim vim介绍 什么是vim&#xff1f; vi和vim是Linux下的一个文本编辑工具。&#xff08;可以李姐为Windows的记事本或word文档&#xff09; 为什么要使用vim&#xff1f; 因为Linux系统一切皆为文件&#xff0c;而我们工作最多的就是修改某个服务的配置&a…

一名七年老安卓的 2022 总结

大家好&#xff0c;我是 shixin。一转眼到了 2022 的最后一天&#xff0c;今年发生了很多事&#xff0c;这篇文章来总结一下。长短期目标达成情况和去年一样&#xff0c;我的长期目标是成为具备创业能力的人&#xff0c;包括商业思维和全栈技术能力。总的来说&#xff0c;今年是…

STM32MP157驱动开发——USB设备驱动

STM32MP157驱动开发——USB设备驱动一、简介1.电气属性2.USB OTG3.STM32MP1 USB 接口简介4.Type-C 电气属性二、USB HOST 驱动开发1.USB HOST 驱动编写2.配置 PHY 控制器3.配置usbh_ehci三、USB HOST 测试1.鼠标键盘驱动使能2.U盘驱动四、USB OTG驱动开发1.USB OTG 控制器节点信…

系统设计实战一

文章目录前言一、服务幂等1.防止订单重复下单1.1 场景如下&#xff1a;当用户在提交订单的时候1.2 重复下单解决方案1.3案例一幂等性总结2 防止订单ABA问题2.1 场景如下&#xff1a;当在修改订单用户信息的时候发生服务器或者网络问题导致的重试2.2 ABA问题解决方案2.3 业务ABA…

Mac本地安装Mysql并配置

文章目录一、安装Mysql二、配置Mysql三、启动mysql四、SQL语法初步了解1.创建数据库2.建表3.查看表一、安装Mysql 笔者推荐采用安装包的方法安装Mysql&#xff0c;比较简单&#xff0c;适合新手。 首先在网上搜安装包&#xff1a; baidu按关键字搜即可&#xff1a;mysql mac安…

多兴趣向量重构用户向量

Re4: Learning to Re-contrast, Re-attend, Re-construct for Multi-interest Recommendation 论文地址&#xff1a;https://arxiv.org/pdf/2208.08011.pdf 一般的多兴趣建模过程是对用户序列进行编码&#xff0c;抽取出用户的多个兴趣向量&#xff0c;然后利用这些用户兴趣向…

【Vue中使用Echarts】echarts初体验

文章目录一、echarts简介二、初次体验echarts1.下载2.在vue中引入echarts①全局引入&#xff08;代码&#xff09;② 局部引入一、echarts简介 在大数据盛行的今天&#xff0c;数据可视化变得越来越广泛。而在前端工作中&#xff0c;数据可视化用得最多的&#xff0c;可能就是…

Usaco Training 刷怪旅 第三层 第四题 :Combination Lock

一个六年级博主写文章不容易&#xff0c;给个关注呗 &#xff08;点赞也行啊&#xff09; 本蒟蒻的bilibili账号 注&#xff1a;这种题当你看不懂的时候是可以把题目复制去洛谷看中文版的 Farmer Johns cows keep escaping from his farm and causing mischief. To try and pre…

如何通过 Python 与 ChatGPT 对话

文章目录简介安装 OpenAI API实例1预备条件: 1. 科学上网&#xff1b; 2. 注册 OpenAI 账号。 简介 ChatGPT 是 GPT-3 语言模型的变体&#xff0c;专为会话语言生成而设计。要在 Python 中使用 ChatGPT&#xff0c;您需要安装 OpenAI API 客户端并获取 API 密钥。当前提你需要…

前端工程师leetcode算法面试必备-二分搜索算法(中)

一、前言 二分搜索算法本身并不是特别复杂&#xff0c;核心点主要集中在&#xff1a; 有序数组&#xff1a;指的是一个递增或者递减的区间&#xff08;特殊情况如&#xff1a;【852. 山脉数组的峰顶索引】&#xff09;&#xff1b; 中间数&#xff1a;用来确定搜索目标落在左…

Pytorch学习笔记①——anaconda和jupyter环境的安装(小白教程)

一、安装Pytorch 1、首先找到anaconda命令端并点击进入。 2、输入如下命令创建子空间&#xff08;博主的命名是pytorch1.4.0&#xff0c;使用python3.6版本&#xff09; conda create -n pytorch1.4.0 python3.6对于下载速度慢的话&#xff0c;首先需要进行换源&#xff0c;换…

FastJson不出网rce

BCEL ClassLoader去哪了 0x01 BCEL从哪里来 首先&#xff0c;BCEL究竟是什么&#xff1f;它为什么会出现在JDK中&#xff1f; BCEL的全名应该是Apache Commons BCEL&#xff0c;属于Apache Commons项目下的一个子项目。Apache Commons大家应该不陌生&#xff0c;反序列化最著…

05 RS485

什么是RS485&#xff1f; RS485 是一种通用的通信标准&#xff0c;广泛用于数据采集和控制应用中。 它的主要优点之一是它允许将多个 RS485 设备放在同一条总线上&#xff0c;这使得多个节点可以相互连接。 RS-485&#xff08;目前称为EIA/TIA-485&#xff09;是通信物理层的…

面试官:React怎么做性能优化

前言 最近一直在学习关于React方面的知识&#xff0c;并有幸正好得到一个机会将其用在了实际的项目中。所以我打算以博客的形式&#xff0c;将我在学习和开发&#xff08;React&#xff09;过程中遇到的问题记录下来。 这两天遇到了关于组件不必要的重复渲染问题&#xff0c;…

2022 年,我身上发生的几件大事

一晃2022互联网寒冬年就要结束了&#xff0c;在今年我的身上发生了好几件人生大事。因为这些事情对我的心态、思绪等产生了不同层次、不同方面的影响&#xff0c;所以很有必要做一次年终复盘。那么&#xff0c;接下来让我用拙略的写作手法&#xff0c;带大家走进我那特别的2022…

别等iPhone14了,苹果iPhone15变化很大

在去年的手机市场当中&#xff0c;苹果可以说是最大的赢家。因为去年iPhone13发布的时间段&#xff0c;恰好是高端旗舰的空档期&#xff0c;小米、OV在高端市场的销量表现一般&#xff0c;华为又没有能力发布新机&#xff0c;三星的Note系列在去年也暂停发布。所以不夸张的说&a…

SpringBoot整合ShardingJdbc实现数据库水平分表实战

(1)添加Maven依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/P…

因果推断4--Causal ML(个人笔记)

目录 1 安装教程及官方文档 1.1 pip安装 1.2 API文档 1.3 代码仓库 2 Uplift模型与主要方法介绍 2.1 发放代金券 2.2 多treatment 2.3 实验方法 3 causalml.inference.tree module 3.1 UpliftTreeClassifier 3.2 UpliftRandomForestClassifier 3.3 CausalRandomFor…

sec7-可派生和非抽象类型

创建非抽象可派生类型比创建抽象类型更常见。本节介绍如何创建非抽象可派生类型对象。派生类型的例子是string的对象。它是TStr。它的子对象是一个数字字符串对象。数字字符串是表示数字的字符串。例如“0”、“-100”、“123.45”。子对象(数字字符串)将在下一节中解释。 我想…

前端框架搭建(九)搭建页面布局框架【vite】

## 1.创建目录BasicLayout——全局布局 components——布局组件 GlobalContent&#xff1a;全局内容GlobalHeader&#xff1a;全局头部页面 2.处理GlobalHeader 创建HeaderMenu——头部菜单 声明相关类型 在typings目录下创建system.d.ts declare namespace App {/** 全局…