【数据结构】单链表详解

当我们学完顺序表的时候,我们发现了好多问题如下:

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

如何解决上面的问题呢??今天就跟着小张一起学习单链表吧!!

链表的概念及结构

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

注意:1.链式结构在逻辑上是连续的,但是在物理上不一定连续
2.结点一般都是在堆上申请出来的(malloc)
3.从堆上申请的空间,是按照一定策略来分配的,两次申请的空间可能连续,也可能不连续

单链表

在这里插入图片描述

单链表的接口实现

void  printdata(info* phead)//打印单链表
info* BuySListNode(int x)//创建新结点
void pushback(info** pphead, int x)//尾插
void pushFront(info** pphead, int x)//头插
void popFront(info** pphead)//头删
void popBack(info** pphead)//尾删
info* SListFind(info* pphead, int x)//单链表查找
SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
void SeqListErase(info** pphead, info* pos)//删除pos位置的值
void SListInsertAfter(info* pos, int x)//单链表在pos位置之后插入x
void SListEraseAfter(info* pos)//删除pos的后一个位置

0.结构体定义单个结点

typedef struct info {int data;//data存数据struct info* next;//info*next存放下一个结点的地址}info;

1.创建新结点

info* BuySListNode(int x)
{info* newnode = (info*)malloc(sizeof(info));//空间申请assert(newnode);//断言,新结点是否申请到了newnode->data = x;//数据赋值newnode->next = NULL;//指向的地址赋值return newnode;//将申请好的空间首地址返回回去}

在这里插入图片描述

断言,如果没申请到空间,mallo返回空指针,断言newnode就会报错

为什么要用二级指针传参在尾插,头插,尾删,头删中

分析:在这里插入图片描述

2.尾插

void pushback(info** pphead, int x)//尾插
{info* newnode = BuySListNode(x);//将创建好的新结点的地址保存在newnode变量中if (*pphead == NULL)//链表无结点{*pphead = newnode;// 将创建好的头节点的地址给给*pphead,作为新头节点的地址}else{info* tail = *pphead;//定义一个指针,先指向头结点的地址while (tail->next != NULL)//循环遍历找尾结点{tail = tail->next;//指针指向下一个结点}tail->next = newnode;//找到尾结点,将尾结点的next存放新接结点的地址}}

分析:
在这里插入图片描述

文字解释:大体上就是直接将最后一个结点的next存入新结点的地址,然后将新结点的next存入空(NULL)。
申请新的结点,如果pphead为空地址,链表还没有结点,将新结点的地址传给pphead,新结点的地址就是头结点的地址,如果该链表中已经存在结点,定义一个指针先存放头节点的地址,然后遍历整个链表,循环寻找哪个结点的next为NULL,不是的话就将tail指向下一个结点,对应操作为tail = tail->next;结点的next为NULL,那个结点就是尾结点,找到尾结点之后将尾结点的next存入要插入新结点的地址,新结点的next已经在 BuySListNode(int x)置为空地址

3.打印链表

void  printdata(info* phead)
{info* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL");}

分析在这里插入图片描述
文字描述:定义一个指针cur指向头结点,使用这个指针循环遍历,这个指针指向的不为空的话就继续循环,在循环中打印cur->data,对应的操作是printf(“%d->”, cur->data);打印%d后面加->是为了方便观察;然后将cur指针移动到下一个结点的位置对应操作是cur = cur->next;,继续打印。

4.头插

void pushFront(info** pphead, int x)//头插
{info* newnode = BuySListNode(x);//创建新结点,将新结点的地址存放在newnode指针变量中newnode->next = *pphead;//新结点的下一个结点应该为旧头结点的地址*pphead = newnode;//新头结点的地址更新为newnode指针所指向的地址}

分析
在这里插入图片描述

文字描述:将创建的新结点的地址存放在newnode指针变量中,pphead为原先头结点的地址,头插一个新结点后,应该将新结点的next存放原先头结点的地址,对应操作为newnode->next = pphead;然后保存在pphead应该为新的头结点的地址,也就是newnode所指向的地址,对应操作为pphead = newnode;

5.头删

void popFront(info** pphead)
{info* p =(*pphead)->next;//定义指针存放头结点的下一个结点的地址free(*pphead);//释放头结点*pphead = p;//头结点地址更新为原先头结点的下一个结点的地址
}

分析:
在这里插入图片描述

6.尾删

void popBack(info** pphead)
{info* tailprev = NULL;info* tail = *pphead;if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{while (tail->next != NULL){tailprev = tail;tail = tail->next;}free(tail);tailprev->next = NULL;}
}

分析:在这里插入图片描述

7.修改

info* SListFind(info* pphead, int x)
{info* cur = pphead;//cur指针保存pphead指针接收的地址while (cur){if (cur->data == x){  //cur->data==y可以将查找出来的值修改为y,不用单独写修改函数,传参也要多一个yreturn cur;//找到的话返回该结点的地址}cur = cur->next;//cur指向下一个结点的地址}return NULL;}

分析:
在这里插入图片描述

8.pos指针指向结点的地址的前一个位置前插插入(随便插)

SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
{assert(pphead);//头结点地址不能为空if (pos == *pphead)//pos指针指向结点的地址为头结点,相当于头插{pushFront(*pphead, x);//调用头插函数}else{info* prev = *pphead;//定义一个prev指针,先让他保存头结点的地址while (prev->next != pos)//循环遍历找pos指针指向结点的前一个结点{prev = prev->next;}info* newnode=BuySListNode(x);//申请新结点,将申请好的结点地址存放在newnode指针变量里面prev->next = newnode;//使之前pos指针指向的结点的前一个结点的next存入插入新结点的地址,newnode->next = pos;//然后新结点的next存入pos指向结点的地址
}
}

分析:在这里插入图片描述

9.删除pos位置的值

void SeqListErase(info** pphead, info* pos)
{if (pos == *pphead)//删除结点是否为头结点{popFront(*pphead);//头删}else{info* prev = *pphead;//定义一个prev指针,先让他存放头结点的地址while (prev->next != pos)//循环遍历寻找哪个结点的next存放的是pos指针指向的结点的地址{prev = prev->next;//prev指针指向下一个结点}prev->next = pos->next;//pos指针指向的结点的上一个结点的next存放pos指针指向的结点的下一个结点的地址free(pos);//释放掉pos指针指向的那块空间}
}

分析:在这里插入图片描述

10.单链表在pos位置之后插入x

void SListInsertAfter(info* pos, int x)
{assert(pos);//防止在空指针info* newnode=BuySListNode(x);//申请新结点newnode->next = pos->next;pos->next = newnode;}

分析:在这里插入图片描述
那么1,2是否可以反过来

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

不行,d3的地址会丢失
出现下图的情况:在这里插入图片描述

11.删除pos的后一个位置

void SListEraseAfter(info* pos)
{assert(pos);//防止pos指向空地址if (pos->next == NULL)//如果pos指针指向最后一个结点return;//直接退出,不往下执行info* del = pos->next;//保存pos指针指向结点的下一个结点的地址pos->next = del->next;//更新pos指针指向的结点的下一个结点为pos指针指向结点下下一个结点的地址free(del);//释放掉保存pos指针指向结点的下一个结点的地址的空间。}

分析:在这里插入图片描述

12.完整源码

#include<stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct info {int data;struct info* next;}info;
void  printdata(info* phead)
{info* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL");}
info* BuySListNode(int x)
{info* newnode = (info*)malloc(sizeof(info));assert(newnode);newnode->data = x;newnode->next = NULL;return newnode;}
void pushback(info** pphead, int x)//尾插
{info* newnode = BuySListNode(x);if (*pphead == NULL){*pphead = newnode;}else{info* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}}
void pushFront(info** pphead, int x)//头插
{info* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;}
void popFront(info** pphead)//头删
{info* p =(*pphead)->next;free(*pphead);*pphead = p;}
void popBack(info** pphead)//尾删
{info* tailprev = NULL;info* tail = *pphead;if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{while (tail->next != NULL){tailprev = tail;tail = tail->next;}free(tail);tailprev->next = NULL;}
}
info* SListFind(info* pphead, int x)//查找
{info* cur = pphead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
{assert(*pphead);if (pos == *pphead){pushFront(*pphead, x);}else{info* prev = *pphead;while (prev->next != pos){prev = prev->next;}info* newnode=BuySListNode(x);prev->next = newnode;newnode->next = pos;}}
void SeqListErase(info** pphead, info* pos)
{if (pos == *pphead){popFront(*pphead);}else{info* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}}
void SListInsertAfter(info* pos, int x)
{assert(pos);info* newnode=BuySListNode(x);newnode->next = pos->next;pos->next = newnode;
}
void SListEraseAfter(info* pos)
{assert(pos);if (pos->next == NULL)return;info* del = pos->next;pos->next = del->next;free(del);}int main()
{info* n1 = (info*)malloc(sizeof(info));info* n2 = (info*)malloc(sizeof(info));info* n3 = (info*)malloc(sizeof(info));info* n4 = (info*)malloc(sizeof(info));assert(n1 && n2 && n3 && n4);n1->data = 1;n2->data = 2;n3->data = 3;n4->data = 4;n1->next = n2;n2->next = n3;n3->next = n4;n4->next = NULL;printf("原链表:");printdata(n1);printf("\n");printf("尾插:");pushback(&n1, 5);pushback(&n1, 6);pushback(&n1, 7);pushback(&n1, 8);printdata(n1);printf("\n");printf("头插:");pushFront(&n1, 10);pushFront(&n1, 20);printdata(n1);printf("\n");printf("头删:");popFront(&n1);printdata(n1);printf("\n");printf("尾删:");popBack(&n1);popBack(&n1);printdata(n1);printf("\n");printf("查找到并修改:");SListFind(n1, 3)->data = 80;printdata(n1);printf("\n");printf("pos指针指向结点的地址的前一个位置前插插入:");SeqListInsert(&n1, n4, 100);printdata(n1);printf("\n");printf("删除pos位置的值:");SeqListErase(&n1, n4);printdata(n1);printf("\n");printf("单链表在pos位置之后插入x:");SListInsertAfter(n2, 1000);printdata(n1);printf("\n");printf("删除pos的后一个位置:");SListEraseAfter(n1);printdata(n1);printf("\n");}

13.代码编译运行

在这里插入图片描述

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

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

相关文章

光栅和矢量图像处理:Graphics Mill 11.4.1 Crack

Graphics Mill 是适用于 .NET 和 ASP.NET 开发人员的最强大的成像工具集。它允许用户轻松向 .NET 应用程序添加复杂的光栅和矢量图像处理功能。 光栅图形 加载和保存 JPEG、PNG 和另外 8 种图像格式 调整大小、裁剪、自动修复、色度键和 30 多种其他图像操作 可处理任何尺寸&am…

AJAX学习笔记1发送Get请求

传统请求有哪些方式,及缺点 传统请求有哪些? 1.直接在浏览器地址栏上输入URL. 2.点击超连接. <a href"/上下文/请求地址">超链接请求</a> ---->相对路径 <a href"http://www.baidu.com">超链接请求</a> ---->绝对路…

【前端】React项目初体验

React介绍 React 是一个非常流行的 JavaScript 前端框架&#xff0c;它为开发人员提供了一种快速构建高质量用户界面的方式。以下是使用 React 构建项目的初体验&#xff1a; 安装 React 和相关依赖项 使用 React 开发项目需要先安装一些必需的依赖项&#xff0c;包括 Node.…

Echarts 中国地图

直接展示效果图&#xff1a; 我们需要引入两个文件&#xff1a; echarts.js 官网地址下载&#xff1a;快速上手 - Handbook - Apache ECharts chain.js 这个官网已经找不到了&#xff0c;需要自行搜寻下载 也可以私信我(网上下载的China.js会导致省名称定为不准确&#xff0…

leetcode 1002. 查找共用字符

2023.9.6 个人感觉这题难度不止简单&#xff0c;考察到的东西还是挺多的。 首先理解题意&#xff0c;可以将题意转化为&#xff1a;求字符串数组中 各字符串共同出现的字符的最小值。 分为三步做&#xff1a; 构造一个哈希表hash&#xff0c;初始化第一个字符串的字母出现频率…

如何挑选低值易耗品管理系统?优化企业管理效率与成本控制

在现代企业管理中&#xff0c;低值易耗品的管理是一个容易被忽视但却十分重要的环节。低值易耗品包括办公用品、耗材、工具等&#xff0c;它们虽然单价不高&#xff0c;但数量庞大且频繁使用&#xff0c;对企业的日常运营和成本控制有着重要影响。为了提高管理效率、降低成本&a…

【Git】删除本地分支;报错error: Cannot delete branch ‘wangyunuo-test‘ checked out at ‘XXX‘

目录 0.环境 1.问题描述 2.解决步骤 1&#xff09;使用命令切换到其他分支 2&#xff09;查看当前本地所有分支 3&#xff09;删除“wangyunuo-test”分支 0.环境 windows 11 64位 Git VScode跑代码 1.问题描述 在做项目过程中&#xff0c;想删除一个本地分支“wangyun…

JS返回NodeList和HTMLCollection详解

HTML DOM 集合 (Collection) 概述 HTML DOM 集合 (Collection) 是一组 HTML 元素&#xff0c;这些元素可以通过 JavaScript 代码进行访问和操作。HTML DOM 集合通常由一个或多个 HTML 元素组成&#xff0c;并提供了访问和操作这些元素的方法。HTML DOM 集合在 JavaScript 中非常…

解决DCNv2不能使用高版本pytorch编译的问题

可变形卷积网络GitHub - CharlesShang/DCNv2: Deformable Convolutional Networks v2 with Pytorch代码已经出来好几年了&#xff0c;虽然声称"Now the master branch is for pytorch 1.x"&#xff0c;实际上由于pytorch自1.11版开始发生了很大变化&#xff0c;原来基…

JAVA毕业设计096—基于Java+Springboot+Vue的在线教育系统(源码+数据库+18000字论文)

基于JavaSpringbootVue的在线教育系统(源码数据库18000字论文)096 一、系统介绍 本系统前后端分离 本系统分为管理员、用户两种角色(管理员角色权限可自行分配) 用户功能&#xff1a; 注册、登录、课程预告、在线课程观看、学习资料下载、学习文章预览、个人信息管理、消息…

elementUI——el-table自带排序使用问题

问题 排序表格默认第一列按降序排&#xff08;状态1&#xff09;&#xff0c;当点击其他列后&#xff08;状态2&#xff09;&#xff0c;改变日期&#xff0c;触发表格数据更新&#xff0c;发现列的排序还点亮在之前的操作上&#xff0c;没有按照默认来&#xff08;回到状态1&a…

Rokid Jungle--Max pro

介绍和功能开发 YodaOS-Master操作系统&#xff1a;以交换计算为核心&#xff0c;实现单目SLAM空间交互&#xff0c;具有高精度、实时性和稳定性。发布UXR2.0SDK&#xff0c;为构建空间内容提供丰富的开发套件 多模态交互 算法原子化 多种开发工具协同 多生态支持 骁龙XR2…

【C++精华铺】10.STL string模拟实现

1. 序言 STL&#xff08;标准模板库&#xff09;是一个C标准库&#xff0c;其中包括一些通用的算法、容器和函数对象。STL的容器是C STL库的重要组成部分&#xff0c;它们提供了一种方便的方式来管理同类型的对象。其中&#xff0c;STLstring是一种常用的字符串类型。 STLstrin…

既然有 HTTP 协议,为什么还要有 RPC

HTTP和RPC 什么是HTTP HTTP协议&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff0c;又叫做超文本传输协议。平时上网在浏览器上敲个网址就能访问网页&#xff0c;这里用到的就是HTTP协议。 什么是RPC RPC&#xff08;Remote Procedure Call&#xff09;&…

VLAN间路由:单臂路由与三层交换

文章目录 一、定义二、实现方式单臂路由三层交换 三、单臂路由与三层路由优缺点对比四、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、定义 VLAN间路由是一种网络配置方法&#xff0c;旨在实现不同虚拟局域网&#xff08;VLAN&#xff09;之…

ssprompt:一个LLM Prompt分发管理工具

阅读顺序 &#x1f31f;前言&#x1f514;ssprompt介绍命令介绍Metafile介绍版本依赖规则 &#x1f30a; PromptHubGitHub Token &#x1f680; Quick Install系统依赖pip安装Linux, macOS, Windows (WSL)Windows (Powershell) &#x1f6a9; Roadmap&#x1f30f; 项目交流讨论…

Android手机防沉迷软件的基本原理

(现在手机游戏、短视频等不仅对小孩子负面影响巨大&#xff0c;连很多成年人都沉迷其中难以自拔&#xff0c;影响工作、生活、学习。这已经造成全社会性的巨大影响&#xff0c;长此以往&#xff0c;国将不国。本人仅在此以自己掌握的些许技术略尽绵薄之力&#xff0c;希望能抛砖…

一、了解[mysql]索引底层结构和算法

目录 一、索引1.索引的本质2.mysql的索引结构 二、存储引擎1.MyISAM2.InnoDB3.为什么建议InnoDB表要建立主键并且推荐int类型自增&#xff1f;4.innodb的主键索引和非主键索引&#xff08;二级索引&#xff09;区别5.联合索引 一、索引 1.索引的本质 索引:帮助mysql高效获取数…

ClickHouse 存算分离改造:小红书自研云原生数据仓库实践

ClickHouse 作为业界性能最强大的 OLAP 系统&#xff0c;在小红书内部被广泛应用于广告、社区、直播和电商等多个业务领域。然而&#xff0c;原生 ClickHouse 的 MPP 架构在运维成本、弹性扩展和故障恢复方面存在较大局限性。为应对挑战&#xff0c;小红书数据流团队基于开源 C…

Vue + Element UI 前端篇(八):管理应用状态

使用 Vuex 管理应用状态 1. 引入背景 像先前我们是有导航菜单栏收缩和展开功能的&#xff0c;但是因为组件封装的原因&#xff0c;隐藏按钮在头部组件&#xff0c;而导航菜单在导航菜单组件&#xff0c;这样就涉及到了组件收缩状态的共享问题。收缩展开按钮触发收缩状态的修改…