C语言_数据结构总结4:不带头结点的单链表

news2025/3/12 23:48:06

纯C语言代码,不涉及C++

0. 结点结构

typedef int ElemType;
typedef struct LNode {
    ElemType data;  //数据域
    struct LNode* next;  //指针域
}LNode, * LinkList;

1. 初始化

不带头结点的初始化,即只需将头指针初始化为NULL即可

void InitLink2(LinkList* L) {
	*L = NULL;
}

2. 头插法

对于不带头结点的单链表,头插法的核心思想是:
每次插入新节点时,将新节点的 next 指针指向当前链表的头节点,然后更新链表的头指针,使其指向新节点。
这样新节点就成为了链表的第一个节点,插入操作的时间复杂度为 O(1)。

int headInsert(LinkList* L, ElemType value) {
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = *L;
	*L = s;  //更新头结点指向新结点
	return 0;  //插入成功
}

3. 尾插法

尾插法的核心思路是每次都将新节点插入到链表的末尾。
对于不带头结点的单链表,需要考虑链表为空的特殊情况。
当链表为空时,新插入的节点就是链表的头节点;
当链表不为空时,需要先遍历到链表的尾部,然后将新节点连接到尾部节点的后面。

int tailInsert(LinkList* L, ElemType value) {
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = NULL;  //因为新结点s要插入到链表尾部
	//若链表为空,新结点就是头结点
	if (*L == NULL)
	{
		*L = s;
	}
	else
	{
		//1.找到链表的尾结点
		LinkList p = *L;
		while (p->next != NULL) {
			p = p->next;
		}
		p->next = s;  //将新节点插入到尾结点后面
	}
	return 0; //插入成功
}

4. 插入

int insertLNode2(LinkList* L, int pos, ElemType value) {
	if (pos < 1) {
		printf("插入位置不合法!\n");
		return -1;
	}
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL) {
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;

	if (pos == 1) {
		s->next = *L;
		*L = s;
	}
	else {
		LinkList p = *L;
		int i = 1;
		while (p != NULL && i < pos - 1) {
			p = p->next;
			i++;
		}
		if (p == NULL) {
			printf("插入位置不合法,已超出链表长度!\n");
			free(s);
			return -1;
		}
		s->next = p->next;
		p->next = s;
	}
	return 0;
}

5. 删除

!不带头结点的单链表进行删除结点操作需要分情况考虑:

若要删除的是头节点,需要直接更新头指针;
若删除的是其他节点,需要找到该节点的前一个节点。

!在不带头结点的单链表删除操作中,当 pos == 1 时,不能直接使用 free(*L);,而要进行 *L = (*L)->next;

直接 free(*L); 存在的问题

free(*L); 这行代码的作用是释放 *L 所指向的内存空间。但执行完这一步后,链表的头指针 *L 仍然指向这块已经被释放的内存,形成了一个野指针。野指针是非常危险的,因为后续如果对这个野指针进行解引用操作(例如访问 (*L)->data 或 (*L)->next),会导致未定义行为,可能会使程序崩溃。而且,由于头指针没有更新,链表的后续节点就无法再被访问到,整个链表就丢失了。

*L = (*L)->next; 操作的意义

当 pos == 1 时,意味着要删除链表的第一个节点(即头节点)。*L = (*L)->next; 这行代码的作用是更新头指针,让它指向原来头节点的下一个节点。具体步骤如下:

  1. 保存原头节点指针

LinkList temp = *L;

》这行代码将原头节点的指针保存到临时变量 temp 中,方便后续释放该节点的内存。
     2. 更新头指针

*L = (*L)->next;

》》将头指针 *L 更新为原头节点的下一个节点。这样,新的头指针就指向了链表的第二个节点(如果存在的话),链表仍然可以正常访问。
   3. 释放原头节点内存

free(temp);

》》》释放临时变量 temp 所指向的内存,即原头节点的内存。

以下删除的操作完整代码:

int deleteNode(LinkList* L, int pos) {
	if (pos < 1)
	{
		printf("删除位置无效!\n");
		return -1;
	}
	if (*L == NULL)
	{
		printf("当前链表为空!\n");
		return -2;
	}
	if (pos == 1)  //即删除头结点,(更新头结点)
	{
		LinkList temp = *L;
		*L = (*L)->next;
		free(temp);
	}
	else
	{
		LinkList p = *L;
		int i = 1;
		//找到第pos-1个结点
		while (p != NULL && i < pos - 1) {
			p = p->next;
			i++;
		}
		if (p == NULL || p->next == NULL)
		{
			printf("删除位置不合法!\n");
			return -1;
		}
		LinkList temp = p->next;
		p->next = temp->next;
		free(temp);
	}
	return 0;  //删除成功
}

6. 按位查找

即:查找第pos个位置上的value值

int findValueByPos(LinkList L, int pos, ElemType* value) {
	if (pos < 1)
	{
		printf("查找位置不合法!\n");
		return -1;
	}
	LinkList p = L;
	int i = 1;
	while (p != NULL && i < pos) {
		p = p->next;
		i++;
	}
	if (p == NULL)
	{
		printf("查找位置超出链表长度!\n");
		return -1;
	}
	*value = p->data;
	return 0;
}

7. 按值查找

即:查找value值在链表的第pos个位置

int findPosByvalue(LinkList L,ElemType value) {
	LinkList p = L;
	int pos = 1;
	while (p != NULL) {
		if (p->data == value)
		{
			return pos;
		}
		p = p->next;
		pos++;
	}
	return -1;  //查找失败,未在该链表中找到该value值
}

8. 链表打印

void printLink2(LinkList L) {
	if (L == NULL) {
		printf("链表为空!\n");
		return;
	}
	LinkList s = L;
	while (s != NULL) {
		printf("%d ", s->data);
		s = s->next;
	}
	printf("\n--------------------------------\n");
}

9. 释放空间

void freeList2(LinkList L) {
	LinkList p = L;
	while (p != NULL) {
		LinkList temp = p;
		p = p->next;
		free(temp);
	}
}

10. 测试代码

int main() {
	//测试插入方法
	LinkList L1;
	InitLink2(&L1);
	insertLNode2(&L1, 1, 11);
	insertLNode2(&L1, 2, 22);
	insertLNode2(&L1, 3, 33);
	printLink2(L1);  // 11 22 33 
	freeList2(L1);

	// 测试头插法
	LinkList L2;
	InitLink2(&L2);
	headInsert(&L2, 1);
	headInsert(&L2, 2);
	headInsert(&L2, 3);
	printLink2(L2);  // 3 2 1
	freeList2(L2);

	// 测试尾插法
	LinkList L3;
	InitLink2(&L3);
	tailInsert(&L3, 1);
	tailInsert(&L3, 2);
	tailInsert(&L3, 3);
	printLink2(L3);  // 1 2 3
	

	// 测试删除
	deleteNode(&L3, 3);
	printf("删除第三个结点后:");
	printLink2(L3);  //删除第三个结点后:1 2

	//测试按值查找
	printf("数值1在第%d个位置\n", findPosByvalue(L3, 1));  // 数值1在第1个位置

	//测试按位查找
	ElemType value;
	findValueByPos(L3, 1, &value);
	printf("第1个位置的值为%d\n", value);  // 第1个位置的值为1

	freeList2(L3);

    return 0;
}

11. 完整代码

#include<stdio.h>
#include<stdlib.h>
/*
    不带头结点的单链表
*/

typedef int ElemType;
typedef struct LNode {
	ElemType data;  //数据域
	struct LNode* next;  //指针域
}LNode, * LinkList;

// 操作1——不带头结点的初始化,即只需将头指针初始化为NULL即可
void InitLink2(LinkList* L) {
	*L = NULL;
}

// 操作2——不带头结点的插入操作
int insertLNode2(LinkList* L, int pos, ElemType value) {
	if (pos < 1) {
		printf("插入位置不合法!\n");
		return -1;
	}
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL) {
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;

	if (pos == 1) {
		s->next = *L;
		*L = s;
	}
	else {
		LinkList p = *L;
		int i = 1;
		while (p != NULL && i < pos - 1) {
			p = p->next;
			i++;
		}
		if (p == NULL) {
			printf("插入位置不合法!\n");
			free(s);
			return -1;
		}
		s->next = p->next;
		p->next = s;
	}
	return 0;
}

//操作2.1——不带头结点的头插法建立单链表方法
int headInsert(LinkList* L, ElemType value) {
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = *L;
	*L = s;  //更新头结点指向新结点
	return 0;  //插入成功
}

//操作2.3——不带头结点的尾插法建立单链表方法
/*
尾插法的核心思路是每次都将新节点插入到链表的末尾。
对于不带头结点的单链表,需要考虑链表为空的特殊情况。
当链表为空时,新插入的节点就是链表的头节点;
当链表不为空时,需要先遍历到链表的尾部,然后将新节点连接到尾部节点的后面。
*/
int tailInsert(LinkList* L, ElemType value) {
	LinkList s = (LinkList)malloc(sizeof(LNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = NULL;  //因为新结点s要插入到链表尾部
	//若链表为空,新结点就是头结点
	if (*L == NULL)
	{
		*L = s;
	}
	else
	{
		//1.找到链表的尾结点
		LinkList p = *L;
		while (p->next != NULL) {
			p = p->next;
		}
		p->next = s;  //将新节点插入到尾结点后面
	}
	return 0; //插入成功
}

// 操作3——删除第pos个位置的值
/*
删除操作需要分情况考虑,若要删除的是头节点,需要直接更新头指针;
若删除的是其他节点,需要找到该节点的前一个节点。
*/
int deleteNode(LinkList* L, int pos) {
	if (pos < 1)
	{
		printf("删除位置无效!\n");
		return -1;
	}
	if (*L == NULL)
	{
		printf("当前链表为空!\n");
		return -2;
	}
	if (pos == 1)  //即删除头结点,(更新头结点)
	{
		LinkList temp = *L;
		*L = (*L)->next;
		free(temp);
	}
	else
	{
		LinkList p = *L;
		int i = 1;
		//找到第pos-1个结点
		while (p != NULL && i < pos - 1) {
			p = p->next;
			i++;
		}
		if (p == NULL || p->next == NULL)
		{
			printf("删除位置不合法!\n");
			return -1;
		}
		LinkList temp = p->next;
		p->next = temp->next;
		free(temp);
	}
	return 0;  //删除成功
}

// 操作4——按位查找:查找第pos个位置上的value值
int findValueByPos(LinkList L, int pos, ElemType* value) {
	if (pos < 1)
	{
		printf("查找位置不合法!\n");
		return -1;
	}
	LinkList p = L;
	int i = 1;
	while (p != NULL && i < pos) {
		p = p->next;
		i++;
	}
	if (p == NULL)
	{
		printf("查找位置超出链表长度!\n");
		return -1;
	}
	*value = p->data;
	return 0;
}

// 操作5——按值查找:查找value值在链表的第pos个位置
int findPosByvalue(LinkList L,ElemType value) {
	LinkList p = L;
	int pos = 1;
	while (p != NULL) {
		if (p->data == value)
		{
			return pos;
		}
		p = p->next;
		pos++;
	}
	return -1;  //查找失败,未在该链表中找到该value值
}

// 操作6——不带头结点的单链表打印操作
void printLink2(LinkList L) {
	if (L == NULL) {
		printf("链表为空!\n");
		return;
	}
	LinkList s = L;
	while (s != NULL) {
		printf("%d ", s->data);
		s = s->next;
	}
	printf("\n--------------------------------\n");
}

// 操作7——释放不带头结点链表内存
void freeList2(LinkList L) {
	LinkList p = L;
	while (p != NULL) {
		LinkList temp = p;
		p = p->next;
		free(temp);
	}
}

int main() {
	//测试插入方法
	LinkList L1;
	InitLink2(&L1);
	insertLNode2(&L1, 1, 11);
	insertLNode2(&L1, 2, 22);
	insertLNode2(&L1, 3, 33);
	printLink2(L1);  // 11 22 33 
	freeList2(L1);

	// 测试头插法
	LinkList L2;
	InitLink2(&L2);
	headInsert(&L2, 1);
	headInsert(&L2, 2);
	headInsert(&L2, 3);
	printLink2(L2);  // 3 2 1
	freeList2(L2);

	// 测试尾插法
	LinkList L3;
	InitLink2(&L3);
	tailInsert(&L3, 1);
	tailInsert(&L3, 2);
	tailInsert(&L3, 3);
	printLink2(L3);  // 1 2 3
	

	// 测试删除
	deleteNode(&L3, 3);
	printf("删除第三个结点后:");
	printLink2(L3);  //删除第三个结点后:1 2

	//测试按值查找
	printf("数值1在第%d个位置\n", findPosByvalue(L3, 1));  // 数值1在第1个位置

	//测试按位查找
	ElemType value;
	findValueByPos(L3, 1, &value);
	printf("第1个位置的值为%d\n", value);  // 第1个位置的值为1

	freeList2(L3);

    return 0;
}

12. 运行截图

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

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

相关文章

几种常见的虚拟环境工具(Virtualenv、Conda、System Interpreter、Pipenv、Poetry)的区别和特点总结

在 PyCharm 中创建虚拟环境是一个非常直接的过程&#xff0c;可以帮助你管理项目依赖&#xff0c;确保不同项目之间的依赖不会冲突。 通过 PyCharm 创建虚拟环境 打开 PyCharm 并选择或创建一个项目。 打开项目设置&#xff1a; 在 Windows/Linux 上&#xff0c;可以通过点击…

Ubuntu安装问题汇总

参考文章&#xff1a; 【Ubuntu常用快捷键总结】 【王道Python常用软件安装指引】 1. 无法连接虚拟设备 sat0:0 【问题】&#xff1a;出现下图所示弹框。 【问题解决】&#xff1a; 点击 “否” 。 点击左上角的 “虚拟机” → “设置…” → “CD/DVD (SATA)” &#xff0c;…

Ceph(1):分布式存储技术简介

1 分布式存储技术简介 1.1 分布式存储系统的特性 &#xff08;1&#xff09;可扩展 分布式存储系统可以扩展到几百台甚至几千台的集群规模&#xff0c;而且随着集群规模的增长&#xff0c;系统整体性能表现为线性增长。分布式存储的水平扩展有以下几个特性&#xff1a; 节点…

从0开始的操作系统手搓教程43——实现一个简单的shell

目录 添加 read 系统调用&#xff0c;获取键盘输入 :sys_read putchar和clear 上班&#xff1a;实现一个简单的shell 测试上电 我们下面来实现一个简单的shell 添加 read 系统调用&#xff0c;获取键盘输入 :sys_read /* Read count bytes from the file pointed to by fi…

【Spring】基础/体系结构/核心模块

概述&#xff1a; Spring 是另一个主流的 Java Web 开发框架&#xff0c;该框架是一个轻量级的应用框架。 Spring 是分层的 Java SE/EE full-stack 轻量级开源框架&#xff0c;以 IoC&#xff08;Inverse of Control&#xff0c;控制反转&#xff09;和 AOP&#xff08;Aspect…

01 音视频知识学习(视频)

图像基础概念 ◼像素&#xff1a;像素是一个图片的基本单位&#xff0c;pix是英语单词picture的简写&#xff0c;加上英 语单词“元素element”&#xff0c;就得到了“pixel”&#xff0c;简称px&#xff0c;所以“像素”有“图像元素” 之意。 ◼ 分辨率&#xff1a;是指图像…

vue3自定义hooks遇到的问题

问题 写了一个输入查询参数和url返回加载中状态、请求方法、接口返回列表的hooks&#xff0c;出现的结果是只有请求方法有效&#xff0c;加载状态无效&#xff0c;接口返回了数据&#xff0c;页面却不显示数据。 代码如下 只展示部分关键代码 import { ref, toRefs, toRef, o…

liunx磁盘挂载和jar启动命令

一、磁盘挂载 查看历史磁盘挂载命令&#xff1a;history | grep mount 查看所有挂载硬盘命令&#xff1a;mount 磁盘挂载命令&#xff1a;mount -t cifs -o usernamesh**,passwordP!ss**** //192.168.1.2/attachmentfilesShare2.2/pdfCert /home/nybzg/cnfai1/pdfCert 二、j…

gbase8s rss集群通信流程

什么是rss RSS是一种将数据从主服务器复制到备服务器的方法 实例级别的复制 (所有启用日志记录功能的数据库) 基于逻辑日志的复制技术&#xff0c;需要传输大量的逻辑日志,数据库需启用日志模式 通过网络持续将数据复制到备节点 如果主服务器发生故障&#xff0c;那么备用服务…

如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统

我在业余时间开发了一款自己的独立产品&#xff1a;升讯威在线客服与营销系统。陆陆续续开发了几年&#xff0c;从一开始的偶有用户尝试&#xff0c;到如今线上环境和私有化部署均有了越来越多的稳定用户。 随时近来 AI 大模型的火热&#xff0c;越来越多的客户&#xff0c;问…

【AI智能体报告】开源AI助手的革命:OpenManus深度使用报告

一、引言&#xff1a;当开源智能体走进生活 2025年3月&#xff0c;MetaGPT团队用一场"开源闪电战"改写了AI Agent的竞争格局。面对商业产品Manus高达10万元的邀请码炒作&#xff0c;他们仅用3小时便推出开源替代品OpenManus&#xff0c;首日即登顶GitHub趋势榜。 …

DeepSeek+Maxkb+Ollama+Docker搭建一个AI问答系统

DeepSeekMaxkbOllamaDocker搭建一个AI问答系统 文章目录 DeepSeekMaxkbOllamaDocker搭建一个AI问答系统前言一、创建同一内网的网络二、拉取两个镜像三、启动Ollama以及调试Maxkb4.Maxkb创建一个应用并建立知识库5、应用效果总结 前言 我觉得只要是使用Docker技术&#xff0c;…

江科大51单片机笔记【12】DS18B20温度传感器(上)

写在前言 此为博主自学江科大51单片机&#xff08;B站&#xff09;的笔记&#xff0c;方便后续重温知识 在后面的章节中&#xff0c;为了防止篇幅过长和易于查找&#xff0c;我把一个小节分成两部分来发&#xff0c;上章节主要是关于本节课的硬件介绍、电路图、原理图等理论…

P8662 [蓝桥杯 2018 省 AB] 全球变暖--DFS

P8662 [蓝桥杯 2018 省 AB] 全球变暖--dfs 题目 解析讲下DFS代码 题目 解析 这道题的思路就是遍历所有岛屿&#xff0c;判断每一块陆地是否会沉没。对于这种图的遍历&#xff0c;我们首先应该想到DFS。 代码的注意思想就是&#xff0c;在主函数中遍历找出所有岛屿&#xff0c…

Vue 侧边栏导航栏 el-menu单个item和多个item

在固钉的下面去写菜单导航栏。 <el-menu class"aside-menu" router :default-active"$route.path" :collapse"isCollapse" background-color"#131b27" text-color"#bfcbd9" active-text-color"#20a0ff" :defau…

Unity Dots从入门到精通之 Prefab引用 转 实体引用

文章目录 前言安装 DOTS 包实体引用Authoring 前言 DOTS&#xff08;面向数据的技术堆栈&#xff09;是一套由 Unity 提供支持的技术&#xff0c;用于提供高性能游戏开发解决方案&#xff0c;特别适合需要处理大量数据的游戏&#xff0c;例如大型开放世界游戏。 本文讲解我在…

无人机避障——XTDrone中运行VINS-Fusion+Ego-planner进行路径规划

本文聚焦于无人机避障技术领域的经典方案&#xff0c;重点探讨视觉双目VINS-Fusion建图与Ego-planner路径规划的组合应用。通过视觉双目VINS-Fusion实现精准的环境建图与自身定位&#xff0c;结合Ego-planner的高效路径规划能力&#xff0c;使无人机在复杂环境中实现自主避障飞…

【沐渥科技】氮气柜日常如何维护?

氮气柜的维护是确保其长期稳定运行、延长使用寿命和保持环境控制精度的关键。以下是沐渥氮气柜的日常维护和定期保养指南&#xff1a; 一、日常维护 柜体清洁 定期用软布擦拭柜体表面和内部&#xff0c;避免灰尘堆积。避免使用腐蚀性清洁剂&#xff0c;防止损伤密封条或传感器。…

MATLAB 控制系统设计与仿真 - 24

PID 控制器分析- 控制器的形式 连续控制器的结构&#xff1a; 为滤波时间常数&#xff0c;这类PID控制器在MATLAB系统控制工具箱称为并联PID控制器&#xff0c;可由MATLAB提供的pid函数直接输入&#xff0c;格式为&#xff1a; 其他类型的控制器也可以由该函数直接输入&#x…

linux(权限)

sudo 主要用来短暂的提权 权限 就是 >角色目标属性 这里面的角色就是---拥有者----所属组----other 所属组的目的&#xff1f; 更细化的管理 chmod 就是修改权限制 我们要是想要切换到体育的账号&#xff0c;我们可以去看一下有几个账号,我…