单链表——单链表的定义及基本操作(初始化、头插法尾插法建表、查找、插入、删除、判空等)

news/2024/11/24 4:26:41/

单链表的定义

由于顺序表存在以下缺陷,所以衍生出了链表,而链表种类有很多种,今天我们讲的是单链表

顺序表存在的问题如下

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

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

链表就像开火车一样,火车头代表开始,后面链接着运送货物的车厢。

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

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构无头单向非循环链表和带头双向循环链表。

无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

今天我们重点来实现无头单向非循环链表的实现俗称单链表

单链表上的操作

 

1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定是连续的。

2.现实中的结点一般都是在堆上malloc申请出来的。

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

4.如果最后一个结点后面没有要存储的值,要把最后一个结点的指针设成NULL,方便下一次的插入新存储的值。

建立单链表

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{SedListdatatype data;struct SedList* next;
}SLNode;

因为我们不知道单链表存储的数据是什么类型的,所以我们把int重定义成 SedListdatatype,如果我们要改其他数据类型,只需要在上面改一下即可。

我们再把结构体的名字也重定义方便实现单链表的功能实现。

单链表不需要初始化,因为我们都是从堆上malloc申请出的空间结点。

数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。 

单链表的打印

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

单链表传过来的指针为空,就打印NULL。因为单链表传过来的指针也可能为空,所以不用断言。

这里while的条件为什么是cur!=NULL?因为这样才能打印单链表的所有存储的值,如果写出cur->next,单链表最后一个元素存储的值就不能打印出来,因为单链表最后一个结点指针为NULL

所以当cur为NULL就可以停止打印。

创建malloc出新结点

SLNode* buynode(SedListdatatype x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

我们要在单链表中实现尾插和头插等等功能,这个结点包括存储的数据和指针(地址)为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点

单链表的头插

void SLPushFront(SLNode** phead, SedListdatatype x)
{assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址SLNode* newnode = buynode(x);newnode->next = *phead;*phead = newnode;
}

单链表的尾插

void SLPushBack(SLNode** phead, SedListdatatype x)
{assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址SLNode* newnode = buynode(x);链表为空if (*phead == NULL){*phead = newnode;
不用让newnode->next=NULL,因为我们在开堆上申请新结点的时候已经完成了这步操作}多个结点SLNode* cur = *phead;while (cur->next != NULL){cur = cur->next;}cur->next = newnode;
}

 当我们遍历单链表的时候,最好给一个另外的变量赋值去遍历,因为我们有时候会需要找到头的地址,为了不丢失头指针的地址,所以我们不用头指针phead自己遍历单链表

另外大家已经注意到为什么头插和尾插使用的是二级指针?因为结构体里面的next本身就是一个一级指针,尾插和头插,都会改变结构体里面存储的数据,而修改一级指针的内容就需要去二级指针来存储一级指针的地址,并传址才能改变单链表的内容,如果使用一级指针来存储一级指针的地址出了循环以后就会销毁,并不会影响单链表的内容也不能增删查改和管理存储数据。相当于形参与实参的差别。形参不会影响实参的改变,只是实参的临时的一份拷贝。所以要用二级指针。

当尾插时链表为空,就相当于头插。不为空时,就找尾再插入即可。

单链表的头删 

void SLPopFront(SLNode** phead)
{assert(phead);assert(*phead);//第一种方法//第二种方法 温柔的检查  选一种即可if (*phead == NULL){printf("链表为空!不能删除!");return;}//只有一个结点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}//多个结点else{SLNode* cur = *phead;*phead = (*phead)->next;free(cur);}}

单链表的尾删

void SLPopBack(SLNode** phead)
{assert(phead);assert(*phead);//第一种方法//第二种方法 温柔的检查 选一种即可if (*phead == NULL){printf("链表为空!不能删除!");return;}//只有一个结点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}//多个结点else{SLNode* cur = *phead;SLNode* pre = NULL;while (cur->next != NULL){pre = cur;cur=cur->next;}free(cur);pre->next = NULL;}}

用一个新指针指向cur指向的上一个结点next,等cur找到最后一个NULL就free,然后再把pre的next设为NULL即可。

单链表的遍历和查找数据 

SLNode* Find(SLNode* phead, SedListdatatype x)
{SLNode* cur = phead;while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;}

函数的返回值不再是void而是一个指针变量,因为我们要把找到的结点地址返回回去,这个函数可以配合修改返回当前结点的值。所以这个函数既能查找又修改单链表结点。

单链表的插入分为pos之前插入和pos之后插入

单链表的pos之后插入

void SLInsertAfter(SLNode* pos, SedListdatatype x)
{SLNode* newnode = buynode(x);newnode->next = pos->next;pos->next = newnode;}

单链表的删除分为pos之前删除和pos之后删除

单链表的pos之后删除

void SLEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* cur = pos->next;pos->next = cur->next;free(cur);}

单链表的销毁

void SLDestory(SLNode** phead)
{SLNode* cur = *phead;while (cur != NULL){SLNode* pre = cur->next;free(cur);cur = pre;}*phead = NULL;
}

在销毁链表的时候不能直接free,因为单链表在物理结构上是不连续存储的,必须要一个一个结点的销毁,再把phead设为NULL。

 

总代码展示

SedList.h
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{SedListdatatype data;struct SedList* next;
}SLNode;void SLprint(SLNode* phead);//打印void SLPushFront(SLNode** phead, SedListdatatype x);//头插
void SLPushBack(SLNode** phead, SedListdatatype x);//尾插void SLPopFront(SLNode** phead);//头删
void SLPopBack(SLNode** phead);//尾删SLNode* Find(SLNode* phead,SedListdatatype x);//查找和遍历void SLInsertAfter(SLNode* pos, SedListdatatype x);//在pos之后插入
void SLEraseAfter(SLNode* pos);//在pos之后删除void SLDestory(SLNode** phead);//销毁
SedList.c
#include"SedList.h"
void SLprint(SLNode* phead)
{SLNode* cur = phead;while (cur != NULL){printf("%d->", cur->data);cur = cur->next;}printf("NULL");printf("\n");
}
SLNode* buynode(SedListdatatype x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}
void SLPushFront(SLNode** phead, SedListdatatype x)
{assert(phead);//即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址SLNode* newnode = buynode(x);newnode->next = *phead;*phead = newnode;
}
void SLPushBack(SLNode** phead, SedListdatatype x)
{assert(phead);SLNode* newnode = buynode(x);//链表为空if (*phead == NULL){*phead = newnode;}//多个结点else{SLNode* cur = *phead;while (cur->next != NULL){cur = cur->next;}cur->next = newnode;}
}
void SLPopFront(SLNode** phead)
{assert(phead);assert(*phead);//第一种方法//第二种方法 温柔的检查if (*phead == NULL){printf("链表为空!不能删除!");return;}//只有一个结点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}//多个结点else{SLNode* cur = *phead;*phead = (*phead)->next;free(cur);}}
void SLPopBack(SLNode** phead)
{assert(phead);assert(*phead);//第一种方法//第二种方法 温柔的检查if (*phead == NULL){printf("链表为空!不能删除!");return;}//只有一个结点if ((*phead)->next == NULL){free(*phead);*phead = NULL;}//多个结点else{SLNode* cur = *phead;SLNode* pre = NULL;while (cur->next != NULL){pre = cur;cur=cur->next;}free(cur);pre->next = NULL;}}
SLNode* Find(SLNode* phead, SedListdatatype x)
{SLNode* cur = phead;while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;}
void SLInsertAfter(SLNode* pos, SedListdatatype x)
{assert(pos);SLNode* newnode = buynode(x);newnode->next = pos->next;pos->next = newnode;}
void SLEraseAfter(SLNode* pos)
{assert(pos);assert(pos->next);SLNode* cur = pos->next;pos->next = cur->next;free(cur);}void SLDestory(SLNode** phead)
{SLNode* cur = *phead;while (cur != NULL){SLNode* pre = cur->next;free(cur);cur = pre;}*phead = NULL;
}
test.c
#include"SedList.h"
void test1()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLprint(plist);}
void test2()
{SLNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLprint(plist);}
void test3()
{SLNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPopFront(&plist);SLprint(plist);}
void test4()
{SLNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPopBack(&plist);SLPopBack(&plist);SLprint(plist);}
void test5()
{SLNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLNode* pos = Find(plist, 3);pos->data = 40;SLprint(plist);}
void test6()
{SLNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLNode* pos = Find(plist, 2);if (pos){SLInsertAfter(pos, 4);}SLprint(plist);}
void test7()
{SLNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLNode* pos = Find(plist, 2);if (pos){SLEraseAfter(pos);}SLprint(plist);}int main()
{test1();test2();test3();test4();test5();test6();test7();return 0;
}


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

相关文章

从力的角度再次比较9-2分布和8-3分布

( A, B )---1*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有1个节点&#xff0c;AB各由11张二值化的图片组成&#xff0c;让A中有3个0&#xff0c;8个1.B中全是0&#xff0c;排列组合A的所有可能&#xff0c;统计迭代次数的顺序。在前面实验中得到了8-3分布的数据 A-B 迭代次数 …

五一欢乐赛!题解

前言 五一欢乐赛有点一言难尽, 这次本来想的给大火减减压放了很多区域赛和省赛的签到题(还有一些是写过的题), 没想到打的并不是很理想, 还用了分数值想减少一些差距, 结果看起来好像让差距更大了, 尴尬 虽然不是很想打击各位, 但是省赛/区域赛的签到题都写成这样, 明年就等着…

分享5款轻量级的Win10神器,错过你会后悔的

今天我要为大家推荐五款小众而且小体积的WIN10小工具&#xff0c;它们可以让你的电脑使用更加方便和高效&#xff0c;而且不占用太多的空间和资源&#xff0c;非常适合轻量级的办公和娱乐。 1.窗口管理工具——TileIconifier TileIconifier可以将窗口最小化到托盘区域,从而更…

perf record对C++程序耗时进行分析

本节将介绍如何使用perf工具的perf record对C代码进行性能分析&#xff0c;一切操作都是在ubuntu 20下进行。 perf工具安装 由于perf工具和内核版本有关&#xff0c;因此直接安装容易出错&#xff0c;建议直接通过如下指令安装&#xff1a; sudo apt-get install linux-tool…

JavaWeb ( 四 ) JavaEE

2.JavaEE 2.1.Java版本 J2SE : 适用于桌面系统的Java 2平台标准版&#xff08;Java 2 Platform Standard Edition&#xff0c;J2SE&#xff09; J2EE : 适用于创建服务器应用程序和服务的Java 2平台企业版&#xff08;Java 2 Platform Enterprise Edition&#xff0c;J2EE&a…

记一次我的漏洞挖掘实战——某公司的SQL注入漏洞

目录 一、前言 二、挖掘过程 1.谷歌语法随机搜索 2.进入网站 3.注入点检测 3.SQLMAP爆破 &#xff08;1&#xff09;爆库 &#xff08;2&#xff09;爆表 &#xff08;3&#xff09;爆字段 三、总结 一、前言 我是在漏洞盒子上提交的漏洞&#xff0c;上面有一个项目叫…

redis服务搭建,C++实现redis客户端,redis远程可视化工具

目录 redis简介redis服务搭建redis常用命令C实现redis客户端redis远程可视化工具:Another Redis DeskTop Manager redis简介 官方网址&#xff1a;https://redis.io/ 开源地址&#xff1a;https://github.com/redis 中文文档&#xff1a;http://www.redis.cn/documentation.ht…

Java 中的线程是什么,如何创建和管理线程-下(十三)

书接上文 CompletableFuture CompletableFuture 是 Java 8 中新增的类&#xff0c;提供了更为强大的异步编程支持。它可以将多个异步任务组合成一个整体&#xff0c;并且可以处理异常情况。 例如&#xff0c;可以使用 CompletableFuture 来实现异步任务的串行执行&#xff1…