【数据结构|C语言版】单链表应用

news2025/3/17 7:27:58

  • 前言
  • 1. 基于单链表实现通讯录
    • 1.1 知识要求
    • 1.2 功能要求
  • 2. 代码总结
    • 2.1 SeqList.h
    • 2.2 SeqList.c
    • 2.3 Contact.h
    • 2.4 Contact.c
    • 2.5 test.c
  • 后言


在这里插入图片描述

上期回顾:【数据结构|C语言版】单链表

前言

各位小伙伴大家好!上期小编讲解了单链表相关知识,在此基础上,我们来学习一下单链表的应用。
在这里插入图片描述

1. 基于单链表实现通讯录

1.1 知识要求

C语言基础要求:结构体、动态内存管理、单链表、文件操作

1.2 功能要求

① 至少能够存储100个人的通讯信息
② 能够保存用户信息:名字、性别、年龄、电话、地址等
③ 增加联系人信息
④ 删除指定联系人
⑤ 查找制定联系人
⑥修改指定联系人
⑦ 显示联系人信息

2. 代码总结

2.1 SeqList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"Contact.h"


//定义链表的结构
//typedef int SLDataType
typedef struct ContactInfo SLDataType;   //更改SLDataType的类型

typedef struct SListNode
{
	SLDataType data;	//保存的数据
	struct SListNode* next;		//指针变量存放下一个节点的地址
}SLNode;

//1 打印链表
void SLPrint(SLNode* phead);

//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//3 头插
void SLPushFront(SLNode** pphead, SLDataType x);

//4 尾删
void SLPopBack(SLNode** pphead);

//5 头删
void SLPopFront(SLNode** pphead);

//SLFind()找到要查找数据的下标
//找节点的函数这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
//缺点:这个函数有一定的局限性,他找到数据x的下标是第一次出现的x的下标后,不会找后续的x的下标
SLNode* SLFind(SLNode** pphead, SLDataType x);

//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);

//8 删除pos节点
void  SLErase(SLNode** pphead, SLNode* pos);

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos);

//10 销毁链表
void SLDestory(SLNode** pphead);

2.2 SeqList.c

#include"SList_copy.h"

//1 创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x)	//保存第一个节点的指针的地址
{
	assert(pphead);
	//创建一个节点
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	//链表不为空,找尾
	SLNode* pcur = *pphead;
	while (pcur->next)//相当于pcur->next!=NULL
	{
		pcur = pcur->next;
	}
	pcur->next = node;
}

//3 头插
void SLPushFront(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

//4 尾删
void SLPopBack(SLNode** pphead)
{
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next)
	{
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点
	//pre->next = NULL;
	pre->next = ptail->next;
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

//5 头插
void SLPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
	//删除pos之后的节点也不能空
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

void SLDestory(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}

2.3 Contact.h

#pragma once
//创建保存联系人数据的结构
#define NAME_MAX 120
#define SEX_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 50

typedef struct ContactInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}ConInfo;
//前置声明
typedef struct SListNode contact;

//1.打开通讯录
void ContactInit(contact** pcon);

//2.保存数据后销毁通讯录
void ContactDestory(contact** pcon);

//3.添加联系人
void ContactAdd(contact** pcon);

//4.删除联系人
void ContactDel(contact** pcon);

//5.修改联系人
void ContactModify(contact* con);

//6.查找联系人
void ContactFind(contact* pcon);

//7.查看通讯录
void ContactShow(contact* pcon);

2.4 Contact.c

#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"
#include"SList_copy.h"

//1.打开通讯录
void ContactInit(contact** pcon)
{
	FILE* pf = fopen("Contact_Info.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen error.\n");  //主动报错,打开失败
		return;
	}
	ConInfo info;
	//回顾一下fread 
	//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
	//从流中读取一个由count元素组成的数组,每个元素的大小为size字节,并将它们存储在ptr指定的内存块中。
	//流的位置指示器按读取的总字节数前进。
	//如果成功读取的总字节数为(size * count)
	while (fread(&info, sizeof(info), 1, pf))
	{
		SLPushBack(pcon, info);
	}
	printf("历史数据已导入通讯录\n");
}

void ContactSave(contact* con)
{
	FILE* pf = fopen("Contact_Info.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen error\n");
		return;
	}
	contact* cur = con;
	while (cur)
	{
		fwrite(&(cur->data), sizeof(cur->data), 1, pf);
		cur = cur->next;
	}
	printf("成功保存通讯录数据\n");
}

void ContactDestory(contact** pcon)
{
	ContactSave(*pcon);
	SLDestory(pcon);
}

void ContactAdd(contact** pcon)
{
	ConInfo info;
	printf("请输入添加联系人姓名:\n");
	scanf("%s", &info.name);
	printf("请输入添加联系人性别:\n");
	scanf("%s", &info.sex);
	printf("请输入添加联系人年龄:\n");
	scanf("%d", &info.age);
	printf("请输入添加联系人电话:\n");
	scanf("%s", &info.tel);
	printf("请输入添加联系人地址:\n");
	scanf("%s", &info.addr);

	SLPushBack(pcon, info);
	printf("已添加联系人数据\n");
}

//我们这里通过姓名删除联系人
contact* FindByName(contact* con, char name[])
{
	contact* cur = con;
	while (cur)
	{
		if (strcmp(cur->data.name,name) == 0)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ContactDel(contact** pcon)
{
	char name[NAME_MAX];
	printf("请输入你要删除的联系人姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(*pcon, name);
	if (pos == NULL)
	{
		printf("该联系人不存在\n");
		return;
	}
	SLErase(pcon, pos);
	printf("已删除该联系人\n");
}

void ContactModify(contact* con)
{
	char name[NAME_MAX];
	printf("请输入你要修改的联系人姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(con, name);
	if (pos == NULL)
	{
		printf("该联系人不存在\n");
		return 1;
	}
	printf("该联系人姓名修改为:\n");
	scanf("%s", pos->data.name);	
	printf("该联系人性别修改为:\n");
	scanf("%s", pos->data.sex);	
	printf("该联系人年龄修改为:\n");
	scanf("%d", &pos->data.age);	
	printf("该联系人电话修改为:\n");
	scanf("%s", pos->data.tel);
	printf("该联系人地址修改为:\n");
	scanf("%s", pos->data.addr);

	printf("修改成功\n");
}

void ContactFind(contact* con)
{
	char name[NAME_MAX];
	printf("请输入你要查找的联系人姓名:\n");
	scanf("%s", name);
	contact* pos = FindByName(con, name);
	if (pos == NULL)
	{
		printf("该联系人不存在\n");
		return;
	}
	printf("找到了\n");
	printf("%s %s %d %s %s\n",
		pos->data.name,
		pos->data.sex,
		pos->data.age,
		pos->data.tel,
		pos->data.addr
	);
}

void ContactShow(contact* con)
{
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
	contact* cur = con;
	while (cur)
	{
		printf("%s %s %d %s %s\n", 
			cur->data.name, 
			cur->data.sex, 
			cur->data.age, 
			cur->data.tel, 
			cur->data.addr);
		cur = cur->next;
	}
}

2.5 test.c

#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"
#include"SList_copy.h"

void test1()
{
	contact *pcontact=NULL;
	ContactInit(&pcontact);
}

void test2()
{
	contact* pcontact = NULL;
	ContactInit(&pcontact);
	ContactAdd(&pcontact);
	ContactDel(&pcontact);
	ContactShow(pcontact);

}

void test3()
{
	contact* pcontact = NULL;

	ContactInit(&pcontact);
	ContactShow(pcontact);
	ContactAdd(&pcontact);
	ContactShow(pcontact);
	ContactDel(&pcontact);
	ContactShow(pcontact);
}
void test4()
{
	contact* pcontact = NULL;

	ContactInit(&pcontact);
	ContactAdd(&pcontact);
	ContactAdd(&pcontact);
	ContactShow(pcontact);
	ContactFind(pcontact);

	ContactShow(pcontact);

}

void test5()
{
	contact* pcontact = NULL;

	ContactInit(&pcontact);
	ContactAdd(&pcontact);
	ContactShow(pcontact);
	ContactModify(pcontact);

	ContactShow(pcontact);

	ContactDestory(&pcontact);
}


void menu()
{
	printf("\n");
	printf("****************************\n");
	printf("****************************\n");
	printf("************通讯录***********\n");
	printf("*********1.添加联系人********\n");
	printf("*********2.删除联系人********\n");
	printf("*********3.修改联系人********\n");
	printf("*********4.查找联系人********\n");
	printf("*********5.查看通讯录********\n");
	printf("*********0.退  出************\n");
	printf("****************************\n");
	printf("****************************\n");
}

int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	//test5();
	int op = 0;
	contact* con=NULL;
	ContactInit(&con);
	do {
		menu();
		printf("请选择:\n");
		scanf("%d", &op);
		switch (op)
		{
		case 1:
			ContactAdd(&con);
			break;
		case 2:
			ContactDel(&con);
			break;
		case 3:
			ContactModify(con);
			break;
		case 4:
			ContactFind(con);
			break;
		case 5:
			ContactShow(con);
			break;
		case 0:
			printf("退出了哈\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (op!=0);
	ContactDestory(&con);

	return 0;
}

后言

以上就是小编对单链表应用的一些初步认识。
如果觉得小编讲的还可以,还请一键三连。互三必回!
持续更新中!
在这里插入图片描述

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

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

相关文章

linux 设置定时任务---学习

1、设置定时任务 crontab -e 设置格式参考&#xff1a;【Linux】Linux crontab 命令定时任务设置_crontab 设置每天10:30执行-CSDN博客 测试过程&#xff1a; */1 * * * * /root/cronjob.sh 脚本内容: echo "hell0 cronjob" >> /root/test/hello.txt 实现…

腾讯云服务器CVM标准型S8实例CPU内存、网络和存储性能测评

腾讯云第八代云服务器标准型S8实例基于全新优化虚拟化平台&#xff0c;CPU采用Intel Emerald Rapids 全新处理器&#xff0c;睿频3.0GHz&#xff0c;内存采用最新DDR5&#xff0c;默认网络优化&#xff0c;最高内网收发能力达4500万pps&#xff0c;最高内网带宽可支持120Gbps。…

游戏生成式 AI:编织梦想,避开阴影

想象一下&#xff0c;一个沉浸式的游戏世界中玩家遇到的每个 NPC 都由 AI 驱动&#xff0c;他们能与玩家进行互动&#xff0c;从改变游戏体验。据 Inword 一项研究显示&#xff0c;绝大多数游戏玩家渴望这种互动&#xff0c;愿意投入更多的时间和金钱来玩这种由 AI 驱动的游戏。…

亚马逊、沃尔玛自养号测评技术解析:如何降低潜在风险

亚马逊等电商平台在全球范围内迅速扩张&#xff0c;竞争愈发激烈。为提升产品排名和销量&#xff0c;众多卖家选择采用自养号测评的策略。然而&#xff0c;自养号测评技术并非完美无缺&#xff0c;它存在着一定的技术局限性。由于缺乏对自养号原理及底层环境搭建的深入理解&…

如何在Vue3中使用H.265视频EasyPlayer.js流媒体播放器?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

操作系统(第五周 第一二堂总结)

目录 回顾 前景知识 概述 定义 进程和线程的关系 进程和线程的区别 线程优缺点 优点&#xff1a; 缺点&#xff1a; 易混概念 线程实现方式 线程的类型&#xff1a; ​编辑 多线程模型&#xff1a; 线程函数 头文件&#xff1a; 线程创建函数&#xff1a; 线…

k8s控制器(五)_____DaemonSet

DaemonSet控制器 DaemonSet控制器是Kubernetes中的一种控制器&#xff0c;用于确保集群中的每个节点都运行一个Pod的副本。它通常用于在整个集群中部署一些系统级别的服务&#xff1a; 在每一个node节点运行一个存储服务&#xff0c;例如gluster&#xff0c;ceph。在每一个no…

DRF视图组件(2个视图基类、5个视图扩展类、9个视图子类、视图集和路由映射)

DRF视图组件(2个视图基类、5个视图扩展类、9个视图子类、视图集和路由映射) 目录 DRF视图组件(2个视图基类、5个视图扩展类、9个视图子类、视图集和路由映射)2个视图基类mixins的5个视图扩展类generics的9个视图子类视图集自定制返回格式自动生成路由(SimpleRouter)action装饰器…

非监督学习的模型为条件概率分布P(z|x)和p(x|z)的区别

在无监督学习中&#xff0c;假设X是输入空间&#xff0c;Z是输出的隐式结构空间&#xff0c;要学习的模型非概率模型情况可以表示为函数zg(x)&#xff0c;概率模型情况下表示为条件概率分布P&#xff08;z|x&#xff09;或p(x∣z)&#xff0c;它们 都可以用来描述数据中的潜在结…

[ROS 系列学习教程] 建模与仿真 - URDF 语法介绍

ROS 系列学习教程(总目录) 本文目录 一、robot标签二、link标签三、joint标签 URDF文件中使用XML格式描述的机器人模型&#xff0c;下面介绍URDF的XML标签。 一、robot标签 机器人描述文件中的根元素必须是robot&#xff0c;所有其他元素必须封装在其中。 属性 name&#x…

JetBrains Rider 2024.1 发布 - 快速且强大的跨平台 .NET IDE

JetBrains Rider 2024.1 发布 - 快速且强大的跨平台 .NET IDE 请访问原文链接&#xff1a;JetBrains Rider 2024.1 (macOS, Linux, Windows) - 快速且强大的跨平台 .NET IDE&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Jet…

jpa使用Querydsl需要规避的一些坑

在使用Spring Data JPA时&#xff0c;通常会使用Querydsl来构建类型安全的查询。在Querydsl中&#xff0c;为了区分实体类与Querydsl查询类&#xff0c;习惯上会给查询类的前缀添加一个"Q"&#xff0c;表示该类是一个查询类。这样做可以有效地避免实体类与查询类之间…

数据结构和算法(哈希表和图(A*算法精讲))

一 、哈希表 1.1 哈希表原理精讲 哈希表-散列表&#xff0c;它是基于快速存取的角度设计的&#xff0c;也是一种典型的“空间换时间”的做法 键(key)&#xff1a; 组员的编号如&#xff0c;1、5、19。。。 值(value)&#xff1a; 组员的其它信息&#xff08;包含性别、年龄和…

pyqt实现星三角减压启动

这个对于plc上实现是非常容易得。它本来就是逻辑控制器&#xff0c;如果用代码实现它&#xff0c;该怎么做呢&#xff1f;这个实现起来看似简单&#xff0c;实则是有不少坑的&#xff08;大神除外&#xff09;。我一直想用类来封装&#xff0c;让它继承QObject,为啥非要继承QOb…

电信网络如何异地共享文件?

电信异地共享文件是指在不同地区的电信网络下&#xff0c;通过使用特定技术实现文件的共享和传输。在传统的网络环境中&#xff0c;由于网络限制和复杂的网络设置&#xff0c;实现跨地区的文件共享是一个具有挑战性的任务。随着技术的不断进步&#xff0c;现在可以利用电信异地…

Spring Boot | SpringBoot对 “SpringMVC“的 “整合支持“、SpringMVC“功能拓展实现“

目录: SpringMVC 的 “整合支持” ( 引入"Web依赖启动器"&#xff0c;几乎可以在无任何额外的配置的情况下进行"Web开发")1.SpringMVC "自动配置" 介绍 ( 引入Web依赖启动器"后&#xff0c;SpringBoot会自动进行一些“自动配置”&#xff0…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《应用图论建模输电网的电力现货市场出清模型》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【cuda\cudnn安装教程】以及环境变量设置(以cuda11.8为例)

【cuda\cudnn安装教程】以cuda11.8为例 cuda11.8安装 安装的时候一切都是按默认安装就好&#xff0c;地址也是默认路径 cudnn安装 下载需要登陆&#xff0c;按要求注册就好 将cudnn压缩包中的内容复制到cuda的安装路径中&#xff0c;进行替换&#xff0c;如下图 验证cuda是否…

可视化报表Superset

文章目录 一、Superset入门与安装1、Superset概述2、安装Python环境2.1 安装Miniconda2.2 创建Python3.7环境 3、Superset部署3.1 安装Superset3.2 启动Supterset3.3 superset启停脚本 4、docker部署 二、Superset使用与实战1、对接MySQL数据源2、制作仪表盘与图表 一、Superse…

【Canvas与艺术】绘制斜置黄色三角biohazard标志

【关键点】 径向渐变色和文字按角度偏转。 【成果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>使用Html5/Canvas绘制…