【数据结构初阶】第三篇——单链表

news/2024/11/22 9:04:05/

链表的概念及其结构

初始化链表

打印单链表

增加结点

头插

尾插

在给定位置之前插入

在给定位置之后插入

删除结点

头删

尾删

删除给定位置的结点

查找数据

修改数据


链表的概念及其结构

基本概念

链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。这里以单链表为例,说明其特性,如图1:

  • 存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素;
  • 长度不固定,可以任意增删;
  • 要访问指定元素,要从头开始遍历元素,直到找到那个元素的位置,时间复杂度为O(N)
  • 在指定的数据元素插入或删除不需要移动其他元素,时间复杂度为O(1)

链表结构:

链表的结构非常多样,一下情况组合起来一共有8种链表结构:

1.单向,双向;2.带头,不带头;3.循环,非循环

单向:只包含指向下一个结点的指针,只能单向遍历;

双向:包含指向下一个结点的指针也包含指向前一个结点的指针,因此可以双向遍历;

带头:1.头结点是为了操作的统一和方便而设立的,放在链表第一个元素之前,其数据域大多无意义,但也可以用来保存链表长度。2.有了头结点,对链表头部的插入和删除操作就统一了。3.头结点不是链表的必要元素。

循环:将尾节点与首节点链接起来,形成了一个环状结构,在某些情况下会非常有用)

虽然有这么多链表的结构,但我们在实际运用中最常见到的还是两种结构:

这里由于篇幅原因,只讲解第一个无头单向非循环链表.

初始化链表

链表是由一个个结点链接而成,创建一个链表之前,我们首先要创建一个结点类型,该类型由两部分组成:数据域指针域

typedef int SLTDateType;
typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;

打印单链表

链表打印就是遍历一遍链表,只不过这里的遍历和数组有点不一样,链表的遍历是判断当前位置是不是为NULL,是就不打印了,不是就打印,通过·cur = cur->next·来移动当前位置。
代码实现如下:

//打印链表
void SListPrint(SListNode* plist)
{SListNode* cur = plist;//接收头指针while (cur != NULL)//判断链表是否打印完毕{printf("%d->", cur->data);//打印数据cur = cur->next;//指针指向下一个结点}printf("NULL\n");//打印NULL,表明链表最后一个结点指向NULL
}

增加结点

每当我们需要增加一个结点之前,我们必定要先申请一个新结点,然后再插入到相应位置,于是我们可以将该功能封装成一个函数。

//创建一个新结点,返回新结点地址
SListNode* BuySLTNode(SLTDataType x)
{SListNode* node = (SListNode*)malloc(sizeof(SListNode));//向新结点申请空间if (node == NULL){printf("malloc fail\n");exit(-1);}node->data = x;//将数据赋值到新结点的数据域node->next = NULL;//将新结点的指针域置空return node;//返回新结点地址
}

头插

1.创建新结点 2.新结点指向原链表 3.头指针指向新结点;(注:2,3顺序不能颠倒,否则新结点将找不到原链表的地址)

//头插
void SListPushFront(SListNode** pplist, SLTDataType x)
{SListNode* newnode = BuySLTNode(x);//申请一个新结点newnode->next = *pplist;//让新结点的指针域指向地址为pos的结点的下一个结点*pplist = newnode;//让地址为pos的结点指向新结点
}

尾插

1.创建新结点2.判断链表是否为空3.为空则让头指针指向新结点4.否则找到最后一个指针,指向新结点;

//尾插
void SListPushBack(SListNode** pplist, SLTDataType x)
{SListNode* newnode = BuySLTNode(x);//申请一个新结点if (*pplist == NULL)//判断是否为空表{*pplist = newnode;//头指针直接指向新结点}else{SListNode* tail = *pplist;//接收头指针while (tail->next != NULL)//若某结点的指针域为NULL,说明它是最后一个结点{tail = tail->next;指针指向下一个结点}tail->next = newnode;//让最后一个结点的指针域指向新结点}
}

在给定位置之前插入

1.创建新结点;2.判断第一个是否是其指定的位置3.如果是再来个头插,否则,找到pos的前一个位置posPrev;4将其新结点插入到pos位置之前,posPrev之后;

 

//在给定位置之前插入
void SListInsertBefore(SListNode** pplist, SListNode* pos, SLTDataType x)
{assert(pos);//确保传入地址不为空SListNode* newnode = BuySLTNode(x);//申请一个新结点if (pos == *pplist)//判断给定位置是否为头指针指向的位置{newnode->next = pos;//让新结点的指针域指向地址为pos的结点*pplist = newnode;//让头指针指向新结点}else{SListNode* prev = *pplist;//接收头指针while (prev->next != pos)//找到地址为pos的结点的前一个结点{prev = prev->next;}newnode->next = prev->next;//让新结点的指针域指向地址为pos的结点prev->next = newnode;//让前一个结点指向新结点}
}

在给定位置之后插入

1.创建新结点; 2.新结点的next指向pos的next;3.pos的next指向newnode;(注:2,3不能交换位置,否则将丢失pos之后的地址)

//在给定位置之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x)
{assert(pos);//确保传入地址不为空SListNode* newnode = BuySLTNode(x);//申请一个新结点newnode->next = pos->next;//让新结点的指针域指向地址为pos的结点的下一个结点pos->next = newnode;//让地址为pos的结点指向新结点
}

删除结点

头删

1.判断链表是否为空;2.头指针指向第一个结点的next;3.释放第一个结点;

//头删
void SListPopFront(SListNode** pplist)
{if (*pplist == NULL)//判断是否为空表{return;}else{SListNode* tmp = *pplist;//记录第一个结点的位置*pplist = (*pplist)->next;//让头指针指向第二个结点free(tmp);//释放第一个结点的内存空间tmp = NULL;//及时置空}
}

尾删

1.判断链表是否为空,为空则则停止删除;2.若只有一个结点,释放其结点,让其链表为空;3.若有两个或两个以上的结点,找到最后一个和其前驱;4.其前驱next为空,释放最后一个结点;

//尾删
void SListPopBack(SListNode** pplist)
{if (*pplist == NULL)//判断是否为空表{return;}else if ((*pplist)->next == NULL)//判断是否只有一个结点{free(*pplist);//释放该结点*pplist = NULL;//及时置空}else{SListNode* prev = *pplist;//接收头指针SListNode* tail = (*pplist)->next;//接收第二个结点的地址while (tail->next != NULL)//当tail指向最后一个结点时停止循环{prev = tail;//使prev始终指向tail的前一个结点tail = tail->next;//tail指针后移}free(tail);//释放最后一个结点tail = NULL;//及时置空prev->next = NULL;//将倒数第二个结点的指针域置空,使其成为新的尾节点}
}

删除给定位置的结点

1.判断链表是否为空;2.如果pos等于第一个结点,则采用头删;3.找出pos的前一个prev 4.将prev的next指向pos的next;5.释放pos;

//删除给定位置的值
void SListErasetCur(SListNode** pplist, SListNode* pos)
{assert(pos);//确保传入地址不为空if (pos == *pplist)//判断待删除的结点是否为第一个结点{*pplist = pos->next;//让头指针指向第二个结点free(pos);//释放第一个结点pos=NULL;//及时置空}else{SListNode* prev = *pplist;//接收头指针while (prev->next != pos)//找到待删除结点的前一个结点{prev = prev->next;}prev->next = pos->next;//让待删除的结点的前一个结点指向待删除结点的后一个结点free(pos);//释放待删除结点pos = NULL;//及时置空}
}

查找数据

//查找数据
SListNode* SListFind(SListNode* plist, SLTDataType x)
{SListNode* cur = plist;//接收头指针while (cur != NULL)//遍历链表{if (cur->data == x)//判断结点是否为待找结点return cur;//返回目标结点的地址cur = cur->next;//指针后移}return NULL;//没有找到数据为x的结点
}

修改数据

//修改数据
void SListModify(SListNode* pos, SLTDataType x)
{pos->data = x;//将结点的数据改为目标数据
}

http://www.ppmy.cn/news/20837.html

相关文章

解决问题的方法论

概述 解决问题的能力是职场中最重要的能力之一,如何逻辑清晰、效率满满的解决问题,可参考以下4个步骤。 一、准确的界定问题 找出真正的问题。 准确的界定问题,避免被表面现象所迷惑。 《麦肯锡工具》中,给出一个标准的步骤&am…

电子技术——基本MOS放大器配置

电子技术——基本MOS放大器配置 上一节我们探究了一种MOS管的放大器实现,其实MOS放大器还有许多变种配置,在本节我们学习最基本的三大MOS放大器配置,分别是共栅极(CG)、共漏极(CD)、共源极&…

易控智驾:用最“接地气”的自动驾驶,写一本“矿区修炼手册”

CES2023刚刚在拉斯维加斯闭幕,作为行业风向标,本届展会上元宇宙、汽车技术等重要科技依然是大亮点。宝马、英特尔等厂商,依然带来了有趣的消费级产品,但也有更多的工业与制造业产品、方案,带着更多的科技智能属性脱颖而…

【C++算法图解专栏】一篇文章带你入门二分算法

✍个人博客:https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 📣专栏定位:为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解,也欢迎大佬们一起交流~ 📚专栏地址:https://blog.csdn.net/Newin…

element 日期组件实现只能选择小时或者只能选择小时、分钟

前言 在使用 element 框架时,总是会有一些满足不了现有项目需求的问题,这个时候就需要我们对 element 的组件进行改造,最近有一个需求就是要求日期组件只能选择年月日时,不要分钟和秒,找了一圈,发现 elemen…

linux内核之netlink通信

Linux内核(04)之netlink通信 Author:Onceday Date:2023年1月3日 漫漫长路,才刚刚开始… 参考文档: netlink 机制 binarydady 阿里云开发者社区linux中通用Netlink详解及使用剖析 binarydady 阿里云开发者社区RFC 3549 Linux N…

【算法基础】双指针算法⭐⭐⭐⭐

一、字符串切分 1. Sample Input abc ppl ldk2. Sample Output abc ppl ldk3. 题解 #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10;

jQuery相较于原生js的优势

原生js的api名字都太长难记原生js有时候代码冗余原生js中有些属性或者方法&#xff0c;有浏览器兼容问题。原生js容错率比较低&#xff0c;前面的代码不能添加多个入口函数(window.onload)&#xff0c;如果添加了多个&#xff0c;后面的会把前面的给覆盖jQuery即library,是一个…