数据结构--单链表

前言

上一章,我们讲了数据结构--动态顺序表,我们会发现有以下问题:

1.当我们要头部或者插入或删除时,都需要进行位置挪动,腾出某一个位置,时间复杂度为0(N)

2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗

3.增容会有一定的浪费空间;例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

下面我们来看看单链表这种线性结构;


链表

概念与结构

链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针

链表中的节点在内存中可以分布在任意位置,不像数组那样需要连续的存储空间。每个节点都包含了存储的数据以及指向下一个节点的指针。通过这种方式,链表可以灵活地分配和管理内存空间。就像一节节连动的火车车厢;

 在数据结构中,呈现:

 逻辑图中,呈现:

 在逻辑图中,链式结构是连续性的,但实际上不一样连续;从数据结构中看出,链表是通过地址来联系在一起的,不需要地址的连续性;在我们要解决链表相关问题时,只需要画出逻辑图即可

 注意:

结点的空间在堆区中开辟;堆区中申请出的空间,会按照一定的策略进行分配,两次申请的空间可能连续,可能不连续;

链表的分类

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

1. 单向或者双向

2. 带头或者不带头
 

3. 循环或者非循环
 

可以通过一定的组合达成不同种类的链表;

这里我们比较常用的是有两种结构:

 

 在这里,我们将先实现无头单向非循环链表,这是链表中结构最为简单的;简称单链表。


单链表的接口实现

先写一下它的结构:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SListNode* next;}SLTNode;

结构体中放入一个数据存储类型和一个结构体指针;结构体指针存放下一个结点的地址;

单链表打印

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

将链表从头到尾遍历一遍,用一个cur指针来进行移动,在while循环中不断遍历打印出结果;打印完就进入下一个结点;

增加链表结点

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

用动态内存分配进行扩容,同时对data和next进行初始化;最后返回结点;

 

尾插

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

这里要注意,我们的形参用到了二级指针,因为当结构体指针为空时,我们就需要对结构体指针进行改变,用二级指针接收结构体指针的地址,能够有效的访问,否则将会报错;当结构体指针不为空时,就利用结构体指针通过循环访问到尾结点,然后在尾结点进行连接;

 

验证:

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);}
int main()
{Test3();return 0;
}

 尾删

void SLPopBack(SLTNode** pphead)
{assert(pphead);//判空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//其他else{SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}
}

当删除的是第一个结点,将会改变结构体指针的地址,所以形参要引用二级指针;其他情况就先找到尾结点,然后进行删除;

 

验证:

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLPopBack(&plist);SLTrint(plist);}
int main()
{Test3();return 0;
}

 头插头删

void SLPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;}void SLPopFront(SLTNode** pphead)
{	assert(pphead);//判空assert(*pphead);//其他SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}

头插相对尾插来说比较容易,因为有头指针,所以不用遍历循环来找到尾结点;并且无论头节点是否为空,操作程序都保持一致;

头删只要找到头结点的下一个结点,那么就可以删除了;

 

验证:

void Test2()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPushBack(&plist, 5);SLTrint(plist);SLPushFront(&plist, 6);SLPushFront(&plist, 7);SLPushFront(&plist, 8);SLPushFront(&plist, 9);SLTrint(plist);SLPopFront(&plist);SLTrint(plist);}int main()
{Test2();return 0;
}

 查找与插入

SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{//判空assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;}

查找:在循环里面通过结点的data与x进行匹配,找到就返回该结点,找不到返回空;如果有多个结点的data与x一致,返回链表最接近头指针的;

插入:是在pos后面进行插入,这样插入比较方便,不用考虑头指针是否为空的问题;

 

验证:

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);}
int main()
{Test3();return 0;
}

删除pos结点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);if (pos == *pphead){SLPopFront(pphead);}else{SLTNode* perv = *pphead;while (perv->next != pos){perv = perv->next;}perv->next = pos->next;free(pos);}
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);//检查尾节点assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);}

第一种删除是删除pos结点,但需要判断该结点是否为首结点;而且需要遍历找到pos结点的前一个结点;比较麻烦;

第二种删除是删除pos结点后一个结点,只需要通过pos结点连接到下下一个结点即可,最后free掉pos的下一个结点;

验证:

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTErase(&plist, pos);SLTrint(plist);}
int main()
{Test3();return 0;
}

 

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTEraseAfter(pos);SLTrint(plist);}
int main()
{Test3();return 0;
}

 摧毁

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

通过记住头结点的下一个结点,free掉头节点,然后头节点的下一个结点成为新的头节点;

验证:

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTDestroy(&plist);SLTrint(plist);
}
int main()
{Test3();return 0;
}

 完整代码

slist.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SListNode* next;}SLTNode;void SLTrint(SLTNode* phead);
SLTNode* BuySListNode(SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);
void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPopBack(SLTNode** pphead);
void SLPopFront(SLTNode** pphead);
SLTNode* SLFind(SLTNode* phead, SLTDataType x);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTEraseAfter(SLTNode* pos);
void SLTDestroy(SLTNode** phead);

slist.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"void SLTrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("mallco fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}void SLPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);if (* pphead == NULL){* pphead = newnode;}else{SLTNode* tail = * pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}}void SLPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;}void SLPopBack(SLTNode** pphead)
{assert(pphead);//判空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//其他else{SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}
}
void SLPopFront(SLTNode** pphead)
{	assert(pphead);//判空assert(*pphead);//其他SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{//判空assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}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(pos);if (pos == *pphead){SLPopFront(pphead);}else{SLTNode* perv = *pphead;while (perv->next != pos){perv = perv->next;}perv->next = pos->next;free(pos);}
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);//检查尾节点assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);}void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* prev = cur;cur = cur->next;free(prev);}*pphead = NULL;
}

test.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"void Test1()
{int n;SLTNode* plist = NULL;printf("请输入链表长度");scanf("%d", &n);printf("请输入值");for (int i = 0; i < n; i++){int val;scanf("%d", &val);SLTNode* newnode = BuySListNode(val);newnode->next = plist;plist = newnode;}SLTrint(plist);
}void Test2()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPushBack(&plist, 5);SLTrint(plist);SLPushFront(&plist, 6);SLPushFront(&plist, 7);SLPushFront(&plist, 8);SLPushFront(&plist, 9);SLTrint(plist);SLPopFront(&plist);SLTrint(plist);}void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTDestroy(&plist);SLTrint(plist);
}
int main()
{Test3();return 0;
}

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

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

相关文章

SpringBoot2.2.0.RELEASE整合Elasticsearch6.8.3

SpringBoot2.2.0.RELEASE整合Elasticsearch6.8.3 SpringBoot是2.2.0.RELEASE&#xff0c;elasticsearch是6.8.3 使用依赖spring-boot-starter-data-elasticsearch 使用ElasticSearchRepository操作 1、导入依赖 <?xml version"1.0" encoding"UTF-8&quo…

VMware Linux Centos 配置网络并设置为静态ip

在root用户下进行以下操作 1. 查看子网ip和网关 &#xff08;1&#xff09;进入虚拟网络编辑器 &#xff08;2&#xff09;进入NAT设置 &#xff08;3&#xff09;记录子网IP和子网掩码 2. 修改网络配置文件 &#xff08;1&#xff09;cd到网络配置文件路径下 [rootlo…

工欲善其事必先利其器,IT工作电脑更要维护好

目录 一&#xff1a;电脑的组成 二&#xff1a;维护措施 三&#xff1a;助力记忆 一&#xff1a;电脑的组成 当谈到电脑主机时&#xff0c;我们通常指的是电脑的中央处理器(CPU)、内存、主板、电源、硬盘、显卡、声卡、网卡等核心部件组成的整体。这些部件共同协作&#xff…

Kafka系列之:记录一次Kafka Topic分区扩容,但是下游flink消费者没有自动消费新的分区的解决方法

Kafka系列之:记录一次Kafka Topic分区扩容,但是下游flink消费者没有自动消费新的分区的解决方法 一、背景二、解决方法三、实现自动发现新的分区一、背景 生产环境Kafka集群压力大,Topic读写压力大,消费的lag比较大,因此通过扩容Topic的分区,增大Topic的读写性能理论上下…

力扣 62. 不同路径

题目来源&#xff1a;https://leetcode.cn/problems/unique-paths/ C题解1&#xff1a;动态规划。声明二维数组。 确定dp数组&#xff08;dp table&#xff09;以及下标的含义。dp[i][j] &#xff1a;表示从&#xff08;0 &#xff0c;0&#xff09;出发&#xff0c;到(i, j) …

Kafka-Broker工作流程

kafka集群在启动时&#xff0c;会将每个broker节点注册到zookeeper中&#xff0c;每个broker节点都有一个controller&#xff0c;哪个controller先在zookeeper中注册&#xff0c;哪个controller就负责监听brokers节点变化&#xff0c;当有分区的leader挂掉时&#xff0c;contro…

一行命令删除tag为<none>的镜像

sudo docker images | grep none | awk {print $3;} | xargs sudo docker rmi

DBeaver开源数据库管理工具发布23.1.3版本

导读DBeaver开源数据库管理软件近日发布了v23.1.3版本,该版本在空间数据查看器、数据传输、数据编辑器等多个模块进行了优化,提升了软件的可用性和兼容性。 具体来看,空间数据查看器新增了地图对象标记和曲线渲染支持,也实现了坐标复制等功能。数据传输模块增强了XLSX文件导入和…

【JVM】什么是双亲委派机制

文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段&#xff0c;而加载阶段需要通过类的全…

Vue2(初识vue)

目录 一&#xff0c;Vue2简介1.1&#xff0c;什么是vue1.2&#xff0c;初始vue1.3&#xff0c;搭建vue环境1.4&#xff0c;第一个hello world 二&#xff0c;基础知识2.1 指令2.2-1 指令v-text2.2-2 指令v-html2.2-3 指令v-if2.2-4 指令v-else2.2-5 指令v-show2.2-6 v-if指令与…

深入学习 Redis - 渐进式遍历 scan 命令、数据库管理命令

目录 前言 一、scan 命令 二、数据库管理命令 select dbsize flushdb / flushall 前言 之前我们所了解到的 keys * 是一次性把整个 redis 中所有的 key 都获取到&#xff0c;但是整个操作比较危险&#xff0c;可能会一下子的都太多的 key&#xff0c;阻塞 redis 服务器. …

centos系统离线安装k8s v1.23.9最后一个版本并部署服务,docker支持的最后一个版本

注意&#xff1a;我这里的离线安装包是V1.23.9. K8S v1.23.9离线安装包下载&#xff1a; 链接&#xff1a;https://download.csdn.net/download/qq_14910065/88143546 这里包括离线安装所有的镜像&#xff0c;kubeadm&#xff0c;kubelet 和kubectl&#xff0c;calico.yaml&am…

linux系统共享文件夹的创建和使用(VMware )

虚拟机设置共享文件夹 点击设置 点击选项 选择共享文件夹 随便添加一个电脑上的文件夹 虚拟机内打开共享文件夹 打开根目录 打开mnt文件夹 继续点击 最终得到共享文件夹

一文讲清多线程与多线程同步

1 多线程 1.1 线程的概念 十多年前&#xff0c;主流观点主张在可能的情况下优先选择多进程而非多线程&#xff0c;如今&#xff0c;多线程编程已经成为编程领域的事实标准。多线程技术在很大程度上改善了程序的性能和响应能力&#xff0c;使其能够更加高效地利用系统资源&…

【读书笔记】CHAPTER 1: SCALE FROM ZERO TO MILLIONS OF USERS

如果你有一定工作经验&#xff0c;或者开发项目基础&#xff0c;就看这个图就可以。 一个“从零扩展到数百万用户”系统&#xff0c;基本由上述部分组成&#xff1a; web、app&#xff1a;触达用户的设备&#xff08;页面&#xff09; DNS&#xff1a;用户看到的都是域名(ww…

count(列名) ,count(1)与count(*) 有何区别?

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 文章目录 一、Mysql之count函数简介二、count(列名) &#xff0c;count(常量)与count(*) 有何区别&#xff1f;2.1 统计字段上的区别2.2 执行效率上的区别 一、Mysql之count函数简介 &#x1f449;表达式 COUNT(…

Centos7 上安装 redis-dump 和redis-load 命令

一、安装rvm 1、安装GPG keys gpg2 --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDBcurl -sSL http://rvm.io/mpapis.asc | gpg2 --import - curl -sSL http://rvm.io/pkuczynski.asc | g…

如何在项目需求与技术方案未确定的情况下掌控上线时间?

需求不明确与技术方案未确定的挑战 在任何项目管理过程中&#xff0c;需求和技术方案是两个核心环节。理想情况下&#xff0c;我们希望在项目开始阶段就有清晰明确的需求和经过深思熟虑的技术方案。然而&#xff0c;现实中的项目管理往往并不如此理想。 项目需求的重要性 需求…

谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结 一、总述 1.1 前端思路 删除和新增以及修改的前端无非就是点击按钮&#xff0c;就向后端发送请求…

将网盘挂载到本地保姆级教程

视频链接&#xff1a;https://www.bilibili.com/video/BV1SA411B7qc Clouddrive2&#xff1a;是一款非常实用的国内网盘服务挂载应用。它支持多种国内知名网盘服务&#xff0c;包括阿里云盘、115云盘、天翼云盘等。通过CloudDrive 2&#xff0c;你可以将这些网盘服务挂载到你的…