首页 > 其他分享 >单链表详解(1)

单链表详解(1)

时间:2024-07-09 13:01:10浏览次数:13  
标签:pphead 单链 SLTNode next 详解 ptail NULL 节点

一、顺序表的弊端

1.往顺序表中间插入元素时时间要移动顺序表的一部分,当顺序表足够大时,这时时间复杂度会时O(n),运行效率会降低;

2.顺序表在空间不够时增容用到realloc函数,这个函数需要拷贝原数据,申请新空间,释放旧空间,这也降低了运行效率;

3.顺序表增容后可能存在空间浪费的问题。

基于此我们引入了链表的概念。

二、单链表的定义

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

单链表的结构:

单链表就像是一节一节车厢构成的火车,每一节都称为一个节点,节点里面有两部分:数据域和指针域;指针域指向的是下一个节点的地址,依次类推;

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

通过next指针我们可以从头一直访问到最后一个节点。

三、单链表的实现

1、头文件

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>

typedef int SLTDataType;

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

//单链表的打印
void SLTPrint(SLTNode* phead);

//申请一个节点
SLTNode* SLTBuyNode(SLTDataType x);

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//尾删
void SLTPopBack(SLTNode** pphead);

//头删
void SLTPopFront(SLTNode** pphead);

//查找节点
SLTNode* SLTNodeFind(SLTNode* phead, SLTDataType x);

//在指定位置之前插入数据
void SLTInsertFront(SLTNode** pphead, SLTNode* pos,SLTDataType x);

//在指定位置之后插入数据
void SLTInsertBack( SLTNode* pos, SLTDataType x);

//删除指定位置的数据
void SLTErase(SLTNode** pphead, SLTNode* pos);

//删除指定位置之后的数据
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SLTDestroy(SLTNode** pphead);

单链表中的数据域 不一定是int型,可能时其他类型,为了方便适应不同情况,我们将单链表中的数据同意重命名为SLTDataType

 测试中定义的单链表是SLTNode* plist=NULL;将这个作为实参传入函数,但是涉及到链表的修改我们需要传地址,所以在函数声明时用到二级指针。

2、函数定义

单链表的打印

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


void test01()
{
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;
	SLTNode* plist = node1;
	SLTPrint(plist);
}
int main()
{
	test01();
	return 0;
}

运行结果:

在这里我们先看到test01里面的内容,这里手续开辟了四个节点的空间,并且给每一个节点的数据域赋值,最后按照顺序利用它们各自的next指针将它们连接在一起;plist代表的是头节点是node1的链表;

接着看到SLTPrint函数:传plist,不需要传地址,打印不涉及到修改;用phead接收,代表链表的头节点,再定义一个pcur代表在遍历过程中此刻是哪个节点;进入循环,最开始是头节点,我们打印它的数据也就是1,然后pcur=pcur->next 表示这个时候pcur成为了第二个节点,因为pcur->next存储的是第二个节点的地址,赋值给pcur就实现了链表节点的遍历,依次打印并且遍历节点,最后到最后一个节点打印完4之后pcur被赋值为NULL,因为最后一个节点的指针域指向的内容NULL;然后循环结束,这样就实现了单链表的打印。

申请节点

上面在打印链表的时候为了方便引入和展示我们是手续申请的节点,实际上,节点的申请只是在需要申请的时候去申请一个,这恰好也是单链表的优点所在,介绍如下:

//申请一个节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

我们在申请节点时,要传入指定的数据作为节点的数据域,在函数内部,定义一个新节点newnode并且使用malloc函数开辟空间,判断开辟是否成功,成功之后,给newnode数据域赋值为传入的数据,再把指针域赋值为NULL,最后返回这个新节点。

单链表尾插

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;//找到最后一个节点
		}
		ptail->next = newnode;
	}
}

尾插传入的是plist的额地址,用二级指针接收,首先断言看pphead是否为空,接着给要尾插的数据申请一个新的节点。尾插时存在两种情况,一是这个链表*pphead是空的,对于空链表直接赋值就行,此时这个新节点也成为了头节点;二是尾插时链表不是空的,那么我们需要先找到尾节点,定义一个节点ptail表示要去找到的尾节点,利用循环通过next去移动此时可以操作到的节点,注意循环条件是ptail->next 因为如果是ptail作为条件最后ptial是NULL而不是最后一个节点,当ptail->next指向NULL时,循环结束,此时ptail就是尾节点,因为在一条单链表中只有尾节点的指针域为NULL。

相关测试:

链表为空时:

 

 链表不为空时

 

 单链表头插

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode=SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

头插在单链表头部插入,此时头节点发生改变,我们使newnode的next指向*pphead,让newnode插入链表并且在称为新的第一个节点,再将*pphead赋值为newnoded,完成新的头节点的设置;

注意此时就算原链表是空的,我们这样写也能确保正常的插入(newnode的next指向NULL;newnode称为新的头节点,指针域是NULL)

相关测试:

 单链表尾删

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	
	else
	{
        SLTNode* ptail=*pphead;
	    SLTNode* pre = *pphead;
		while (ptail->next)
		{
			pre = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		pre->next = NULL;
	}
}

首先单链表要进行删除操作,要先保证单链表不为空(*pphead!=NULL)

如果单链表只有一个节点那么就直接释放头节点并赋为NULL;若是不为空我们需要找到尾节点以及尾节点之前的节点,因为除了要释放最后一个节点之外还要把倒数第二个节点的next置为空;利用循环,当ptail成为最后一个节点时,pre在ptail赋值操作之前那么pre就是倒数第二个节点。找到这两个结点之后,释放尾节点,并赋为空,最后将倒数第二个节点的next赋为空。

单链表头删

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

头删会把头节点释放,如果不提钱存放头节点直接释放的话之后就找不到链表后面的节点了,所以先定义一个next节点保存头节点的下一个节点。在释放完头节点之后,让下一个节点成为新的头节点。

标签:pphead,单链,SLTNode,next,详解,ptail,NULL,节点
From: https://blog.csdn.net/zc331/article/details/140286954

相关文章

  • 3.2 Ansible lineinfile模块详解
    1简介之所以专门说一说这个模块,是因为lineinfile在实际使用中非常有用。lineinfile模块用于在源文件中插入、删除、替换行,和sed命令的功能类似,也支持正则表达式匹配和替换。实际上,在大多数时候,我们在linux上的操作,就是针对文件的操作,通过配置管理工具对配置文件作统一的配置修......
  • 2 Ansible Inventory配置详解
    在使用Ansible来批量管理主机的时候,通常我们需要先定义要管理哪些主机或者主机组,而这个用于管理主机与主机组的文件就叫做Inventory,也叫主机清单。AnsibleInventory是包含静态Inventory和动态Inventory两部分的,静态Inventory指的是在文件中指定的主机和组,动态Inventory......
  • 详解Web应用安全系列(10)文件上传漏洞
    文件上传漏洞(FileUploadVulnerabilities)是Web攻击中常见的一种安全漏洞,它允许攻击者上传并执行恶意文件,从而可能对Web服务器造成严重的安全威胁。一、定义与原理文件上传漏洞是指Web应用程序在处理用户上传的文件时,由于缺乏对上传文件的类型、大小、内容等属性的严格检查和......
  • MySQL 进阶(二)【索引详解】
    前言    程序员避不开和数据库打交道,大数据更是如此,不管是MySQL、Oracle、SQLServer这些OLTP数据库,还是Greeplum、StarRocks、Hive、SparkSQL、FlinkSQL、ClickHouse等OLAP数据库,SQL都是最基础最重要的能力,数据库知识也是每一个程序员必备的知识。  ......
  • Python的utils库详解
    Python的utils库并不是一个官方标准库,而是指一系列提供实用功能的工具库或模块,这些库或模块通常包含了一系列帮助开发人员加速日常工作、提高开发效率的工具函数或类。由于Python社区的开放性和活跃性,存在多个不同的utils库,每个库都有其特定的功能和用途。不过,尽管没有一个统一......
  • tar 命令详解
    tar命令 [root@linux~]# tar[-cxtzjvfpPN]文件与目录....Usage:tar[OPTION...][FILE]...Examples:    tar-cfarchive.tarfoobar     #Createarchive.tarfromfilesfooandbar.    tar-tvfarchive.tar         ......
  • 深度学习 - 模型剪枝技术详解
    模型剪枝简介模型剪枝(ModelPruning)是一种通过减少模型参数来降低模型复杂性的方法,从而加快推理速度并减少内存消耗,同时尽量不显著降低模型性能。这种技术特别适用于资源受限的设备,如移动设备和嵌入式系统。模型剪枝通常应用于深度神经网络,尤其是卷积神经网络(CNNs)。模型剪......
  • ASP.NET-框架分类与详解
    本文介绍了ASP.NET框架,涵盖了WebForms的事件驱动模型、MVC的解耦结构和WebAPI的HTTP服务构建。讨论了三种框架的特点、适用场景及开发流程,强调了ASP.NET在企业级Web开发中的重要性.一、ASP.NET框架概述ASP.NET是由微软公司推出的一种基于.NET框架的服务器端Web应用程序开发技术。......
  • 线性表——单链表
    #include<bits/stdc++.h>usingnamespacestd;typedefstructLNode{ intdata; structLNode*next;}LNode,*List;//初始化voidInitList(List&L){ L=(LNode*)malloc(sizeof(LNode)); L->next=NULL;}//头插voidListInsertHead(List&L,inte)......
  • Redis复制过程详解
    主从复制简介  主从复制是为了达成高可用,即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用。  一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。 主从复制的作用  读写分离:主节点写,从节点读,提高服务器的读写负载能......