数据结构-链表

🗡CSDN主页:d1ff1cult.🗡

🗡代码云仓库:d1ff1cult.🗡

🗡文章栏目:数据结构专栏🗡

目录

目录

代码总览:

接口slist.h:

slist.c:

1.什么是链表

1.1链表的概念

 1.2链表的分类

2.链表与顺序表

顺序表的优点与缺陷

3链表

3.1链表实现

总结



代码总览:

slist.h:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead);SLTNode* BuySListNode(SLTDataType x);
//开辟新的节点void SLTPushBack(SLTNode** pphead, SLTDataType x);
//尾插
void SLTPushFront(SLTNode* phead, SLTDataType x);
//头插
void SLTPopBack(SLTNode** pphead);
//尾删
void SLTPopFront(SLTNode**  pphead);
//头删
void SLTInsert(SLTNode**phead,SLTNode* pos, SLTDataType x);
//在pos之前插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//在pos之后插入x
void SLTErase(SLTNode** pphead, SLTNode*pos);
//删除pos位置
void SLTEraseAfter(SLTNode* pos);
//删除pos后一个位置
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//查找 

slist.c:

#define _CRT_SECURE_NO_WARNINGS
#include"slist.h"
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}//头插
void SLTPopBack(SLTNode** pphead, SLTDataType x)
{//1.空assert(*pphead);//2.一个节点//3.一个以上节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{/*SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;*/SLTNode* tail = *pphead;while(tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
void SLTPopFront(SLTNode** pphead)
{assert(*pphead);//空//非空SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data== x){return cur;}cur = cur->next;}return NULL;
}
void SLTInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x)
{assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);posNext = NULL;
}

1.什么是链表

1.1链表的概念

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

96cd35bb14c440be979abea5657c77fe.png

 2fe2c859e4054c178d5ede5c3ae66ce3.png

 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续现实中的结点一般都是从堆上申请出来的从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

22587d64336a4323b7a0a51a0da775bf.png

 1.2链表的分类

1.带头或不带头

2.循环或不循环

3.双向或单向

67297e973b054a16a8cddcafe624fc59.png

2.链表与顺序表

顺序表的优点与缺陷

顺序表的数据在物理空间上是连续存放的,使得我们用数组下标访问变的十分简单,尾插尾删的效率比较高,但同时想要在中间删除数据时,需要将数据一个个挪动,效率比较低,但同时他的数据在物理空间上连续存放也有了以下的问题

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

详细了解请移步:

3链表

链表是一块一块独立的空间,通过结构体指针来连接,删除/插入数据比较方便,空间使用malloc函数开辟,浪费的空间比较少,而且在物理上不是连续存放的,不会出现异地扩容之类的行为,对空间懂得利用较高,空间都是按需申请的

3.1链表实现

1.定义一个结构体

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

同时为了方便改变数据的类型,我们定义typedef int SLTDataType;结构体中的内容有存储的数据还有指向下一块内容的结构体指针 next

2.链表的遍历

void PrintSlist(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("\n");
}

 这其中有一句非常关键的代码:cur=cur->next;这个时候又得拿出这张图了:

385cc3a008a04895a3ed6817e0e61fb0.png

将头指针phead赋值给cur,让cur不断向后遍历,直到遇到NULL指针停下来

那么cur=cur->next;首先cur=phead,此时cur指向了第一个结构体,打印data

然后再将结构体中next指向的地址赋值给cur,此时cur就指向了下一个结构体,

如此遍历直到cur遇到了NULL;

3.创建新节点

定义一个结构体指针newnode用来创建新的节点,使用malloc手动开辟所需的空间,需要注意的是newnode是结构体指针,类型是SLTNode而不是SLTDataType,如果定义成了SLTDataType类型,后续的free操作就会报错

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

 4.尾插

引入了新的结构体指针tail,不断遍历链表,直到tail->next==NULL的时候说明已经找到了链表的尾部,此时再将创建好的newnode赋值给tail->next将新节点和链表链接起来实现了链表的尾插。  注意,while中tail->next==NULL,不能写成tail==NULL,tail=newnode。

因为phead newnode tail都是形参出了作用域后就会销毁,这样写不仅实现不了尾插,还会导致内存的泄露。

下面这个图就是错误原因

81dd5d1672074efc8e48ccdf4c0bdcbb.png

这一段尾插没有考虑链表为空的情况。

void SLTPushBack(SLTNode* phead, SLTDataType x) 
{SLTNode* newnode = BuySListNode(x);SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}//尾插

 下面为正确的尾插,参数需要传二级指针

1.改变结构体需要用结构体指针(参数)

2.改变结构体指针需要用结构体指针的指针(tail->next = newnode)

void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

 5.头插

分为三种情况

1.空

2.一个节点

3.一个以上的节点(两种写法)

当找到尾部tail后不能直接将tail   free释放掉然后置空,因为上一个结构体还指向tail,然而tail又是局部变量,出了作用域就会被销毁,所以上一个结构体的next指向了野指针,所以我们就想到了使用tail的前一个 tailPrev,避免了野指针的出现;

链表的头插,,简单而且效率高

void SLTPopBack(SLTNode** pphead, SLTDataType x)
{//1.空assert(*pphead);//2.一个节点//3.一个以上节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{/*SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;*/SLTNode* tail = *pphead;while(tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

 6.头删

同样考虑是否为空,较为简单,头删简单效率高。

void SLTPopFront(SLTNode** pphead)
{assert(*pphead);//空//非空SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}

7.查

这里的查也同时可以起到修改的作用,查找到数据后,将其内容修改。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data== x){return cur;}cur = cur->next;}return NULL;
}//修改:
//SLTNode* pos = SLTFind(plist, 1);//if (pos)//{//	pos->data *= 10;//}

8.在pos位置前插入

(其实相当于在pos位置插入)在pos位置前插入,需要链表遍历到pos,但是此时不知道pos前一个位置的地址,无法做到在pos位置前插入,所以我们又引进了一个结构体指针prev,指向了pos的前一个位置.

单链表的前插还是具有一定的局限性,前插用双向链表来实现更为便捷。

void SLTInsert(SLTNode** pphead,SLTNode* pos, SLTDataType x)
{assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}}

 9.在pos位置后插入

注意

newnode->next = pos->next;
pos->next = newnode;

这两句代码的顺序不能改变,不然就会出现环链表,,从而导致死循环

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

 10.删除pos位置

首先判断一下pos是不是头指针,如果是就复用之前写的头删,如果不是,就选择引入prev指针指向pos的前一个位置,方便与pos后面的指针链接

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}

11.删除pos位置后一个

较为简单不再赘述

void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);posNext = NULL;
}

 12.销毁

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

总结

以上便是不带哨兵 单链表的实现。

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

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

相关文章

java多线程并发面试题总结(史上最全40道)

1、多线程有什么用&#xff1f; 一个可能在很多人看来很扯淡的一个问题&#xff1a;我会用多线程就好了&#xff0c;还管它有什么用&#xff1f;在我看来&#xff0c;这个回答更扯淡。所谓"知其然知其所以然"&#xff0c;"会用"只是"知其然"&am…

用C语言构建一个数字识别卷积神经网络

卷积神经网络的具体原理和对应的python例子参见末尾的参考资料2.3. 这里仅叙述卷积神经网络的配置, 其余部分不做赘述&#xff0c;构建和训练神经网络的具体步骤请参见上一篇: 用C语言构建一个手写数字识别神经网路 卷积网络同样采用简单的三层结构&#xff0c;包括输入层con…

思想道德与法治

1【单选题】公民的基本权利是指宪法规定的公民享有的基本的、必不可少的权利。公民的基本权利有不同的类别&#xff0c;公民的通信自由和通信秘密属于 A、人身自由 B、经济社会权利 C、政治权利和自由 D、教育科学文化权利 您的答案&#xff1a;A 参考答案&#xff1a;A 查…

Visual Studio 快捷键

记录一下VS的快捷键,用Xcode几个星期后回到VS一下子有点乱,还好有条件反射在,过了会就都恢复了 目录 跳转快捷键查找快捷键编辑快捷键代码折叠书签操作记忆来源VS一定要装VAssistX插件,下面的快捷键部分是VX提供的。 跳转快捷键 快速打开文件 Alt + Shift + O 快速打开对…

VSCode C/C++多文件编译配置

多文件编译备忘&#xff0c;带注释的地方都需要注意&#xff01;&#xff01;&#xff01; launch.json文件 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387&quo…

ChatGPT在语言辅助翻译和跨文化交流中的应用如何?

ChatGPT在语言辅助翻译和跨文化交流领域中有广泛的应用潜力&#xff0c;可以帮助人们克服语言障碍&#xff0c;促进跨文化交流和理解。以下是详细的讨论&#xff1a; **1. 实时翻译和即时交流&#xff1a;** ChatGPT可以用于实时翻译&#xff0c;使人们能够即时进行跨语言交流…

LNMP搭建以及Discuz论坛部署

目录 LNMP 编译安装 LNMP搭建 Nginx 服务 MySQL 服务 PHP 解析环境 部署 Discuz社区论坛 LNMP 目前成熟的企业网站的应用模式之一&#xff0c;指的是一套协同工作的系统和相关软件&#xff0c;能提供静态页面服务和动态web服务 L linux系统 N nginx网站服务&#xff0…

社区团购行业的解决方案:重塑业务模式,提升效率和质量

社区团购业务正在中国迅速崭露头角&#xff0c;而随着行业的快速发展&#xff0c;也带来了一系列挑战&#xff0c;包括供应链管理、物流配送、产品质量和用户体验等问题。本文将探讨这些问题&#xff0c;并提出一些可能的解决方案。 一、问题和挑战 1.1 供应链管理 对于社区团…

k8s pod数据存储Volumes

一、说在前面的话 在 Kubernetes 的 Deployment 中&#xff0c;您可以使用多种类型的 Volumes 来管理 Pod 中的数据。 作用是用来共享目录及配置&#xff0c;不用在每个pod里进行配置。 本文主要概述怎么使用HostPath、PersistentVolumeClaim、ConfigMap。 二、k8s有哪些Vol…

OC实现GZIP压缩及解压缩

这恍恍的天日晒的大地嗞嗞的作响。这湉湉的阴雨下的祖国母亲到处洪水泛滥。人本不该有三六九等&#xff0c;可这丑陋的阴雨竟然选择性的泄洪到无辜的县区以示人却有三六九等。谁的财产不是财产&#xff0c;谁的生命不是生命&#xff1f;谁特妈的不是母亲养大的&#xff1f; 一首…

C#核心知识回顾——21.归并排序

理解递归逻辑 一开始不会执行sort函数的 要先找到最小容量数组时 才会回头递归调用Sort进行排序 基本原理 归并 递归 合并 数组分左右 左右元素相比较 一侧用完放对面 不停放入新数组 递归不停分 分…

《golang设计模式》第一部分·创建型模式-04-抽象工厂模式(Abstract Factory)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 1.1 角色 AbstractFactory&#xff08;抽象工厂&#xff09;&#xff1a;它声明了一组用于创建产品的方法&#xff0c;每一个方法对应一种产品。ConcreteFactory&#xff08;具体工厂&#xf…

Python学习笔记:If、While

1.if if的基本结构&#xff1a; if xxxxx:xxxxxxx elif xxxx:xxxxxxx else:xxxxxxx 通过boolean判断的实例 is_hot True is_cold True if is_hot:print("its a hot day\nDrink plenty of water") elif is_cold:print("its a cold day\nWear warm clothes&…

【网络|TCP】三次握手、四次握手

TCP是一种面向连接的可靠的传输协议&#xff0c;建立和断开TCP连接时需要进行握手的过程。其中&#xff0c;TCP的连接建立需要进行三次握手&#xff0c;而连接断开则需要进行四次握手。 解释 三次握手 第一次握手&#xff1a;客户端发送一个SYN&#xff08;同步&#xff09;报…

vue el-input 使用 回车键会刷新页面的问题

场景&#xff1a; vue项目中 在输入框输入字符并按下回车键搜索时&#xff0c;不会进行搜索&#xff0c; 而是会刷新页面 原因&#xff1a; 当form表单中只有一个input时&#xff0c;按下回车建会自动触发页面的提交功能&#xff0c; 产生刷新页面的行为 解决&#xff1a; 在…

问题聚集度Hive SQL

问题聚集度&#xff1a;最小的分母占比&#xff0c;贡献最多的分子占比&#xff0c;即小规模贡献大问题。 selectcity_name,user_id,rf_type,deal_ord_cnt,sale_amt,rf_ord_cnt,rf_amt,rf_ra,rf_amt_ra,rf_all,ord_cnt_all,rf_gx,ord_cnt_gx,del_gx,row_number() over(partiti…

Spring 事务详解(注解方式)

目 录 序言 1、编程式事务 2、配置声明式事务 2.1 基于TransactionProxyFactoryBean的方式&#xff08;不常用&#xff0c;因为要为每一个类配置TransactionProxyFactoryBean&#xff09; 2.2 基于AspectJ的XML方式&#xff08;常用&#xff0c;可配置在某些类下的所有子…

docker: CMD和ENTRYPOINT的区别

ENTRYPOINT&#xff1a; 容器的执行命令&#xff08;属于正统命令&#xff09; 可以使用--build-arg ENVIROMENTintegration参数覆盖 ocker build --build-arg ENVIROMENTintegration 两者同时存在时 CMD作为ENTRYPOINT的默认参数使用外部提供参数会覆盖CMD提供的参数。 CMD单…

无涯教程-Perl - unless...else 语句函数

Perl 除非语句后可以跟可选的 else 语句&#xff0c;该语句在布尔表达式为true时执行。 unless...else - 语法 Perl编程语言中的unless... else 语句的语法为- unless(boolean_expression) {# statement(s) will execute if the given condition is false } else {# stateme…

编程导航算法村第八关 | 树的深度优先遍历

编程导航算法村第八关 | 树的深度优先遍历 判断两棵树是否相同 LeetCode100&#xff1a;给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。思路&…