数据结构之链表篇

news2024/11/19 11:18:14

8a6bc8c8433f4ff4aa919cec1573965d.png

今天我们讲我们数据结构的另一个重要的线性结-----链表,

什么是链表

链表是一种在 物理存储上不连续,但是在逻辑结构上通过指针链接下一个节点的形成一个连续的结构。

他和我们的火车相似,我们的元素是可以类比成车厢,需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。

这就挺现了一个好处:就是我们在插入删除的时候不用将其他的元素进行移动,我们只需要将指针的指向进行改变就行。所以在我们考虑使用顺序表还链表的时候,就可以考虑我们是否需要频繁的插入和删除我们的元素。

链表的在机内的存储

我们的链表在机内存储是不连续的,是分散的,我们要想找到我们的下一个节点就需要一个指针指向我们的下一个节点,这样来考虑的话我们的每个节点就需要一个数据域和一个指针域,用一个结构体。

为什么还需要指针变量来保存下⼀个节点的位置? 链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),我们需要通过指针 变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。 

108dfbe5a2db49b3973bab3d0d4177d2.png

 那我们的节点的结构体就可以写出来了:

struct SListNode
{
 int data; //节点数据 
 struct SListNode* next; //指针变量⽤保存下⼀个节点的地址 
};

 链表的特点

由于链表在存储空间是上不是连续的,我们在来插入删除的时候就不需要去移动,使我们的时间复杂度降低。

而且我们的链表是用一个节点申请一个节点,不会出现节点空间不够和空间浪费的现象。

 但是这不方便我们经常访问元素,因为我们要从头节点一个一个的去遍历,每次的访问都需要我们从头开始这就导致我们的访问不方便,是顺序存取。

用链表来实现接口

我们的来拿表有很多种分类,有这几种特性结合在一起:带不带头(就是我们的哨兵位),循不循环,是双向的还是单向的;

f073b0a743c84179b02772299c5df403.png

我们这里使用的是不带头的单链表。

我们用链表来实现一些接口:增删查改等。

我们先来完成我们的链表的插入:头插和尾插;

我们有一个功能就是我们需要去频繁地申请空间,这样我们可以去包装成一个函数:

创建新的节点

创建新的节点其实就是使用我们动态申请空间函数的应用了,malloc和calloc。

在我们使用完这个函数之后,我们还需要去判断一下是否申请成功。如果空间申请不成功我们可以报个错。

代码:

//申请新的节点
SLTNode* SLBuyNode(SLTDataType x)
{
	SLTNode* newNode = (SLTNode*)malloc (sizeof(SLTNode));
	if (newNode==NULL)
	{
		perror("malloc:");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

头插:

我们的头插是在我们我们的第一个节点的前面插入,如果我们有哨兵位的话就是在 哨兵位的后面进行插入,我们这里实现的是不带哨兵位的。

那我们这里就有2个问题:就是当我们的链表为空时,我们应该怎么办,和我们的形参参应该是一级指针还是二级指针?

其实当我们的链表为空时,和我们不为空时是一样的处理方法,先创建一个新的节点,使这个新节点的下一个节点指向我们的刚开始的头节点,如果为空,新节点的next指针就指向空

而对于我们的传参,我们应该知道的是,我们在主函数中创建的是一个ListNode*的结构体指针变量phead,我们如果传一级指针的话,那我们的形参就是我们实参的一个拷贝,我们形参的改变并不会改变影响到实参,但是我们在进行头插的时候我们是需要改变我们的实参phead的指向的,所以我们就需要传我们的地址,而我们的一级指针的地址,需要用二级指针来接收。

所以我们这两个问题解决了就可以两我们的代码写出来了。

原来的链表:

插入之后

代码:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = NULL;
	newNode = SLBuyNode(x);
	newNode->next = *pphead;
	*pphead= newNode;
}

尾插:

我们的尾插就是在我们的链表中的尾部插入,而我们的尾插比头插麻烦的是,我们寻找我们的链表的尾部,因为我们的链表在机内的存储是分开的,我们并不知道我们的尾节点在哪,只能从头开始遍历,找到我们的尾节点,而我们的尾节点是next指针为空的节点,我们通过一个while循环即可,

但是这里又有一个不同的是,当我们的链表为空时,和我们不为空时的是不同的,为空时我们需要将我们的phead指针指向我们的新节点,而不为空时就需要我们去遍历找我们的尾节点,这也告诉我们这里需要传地址,用二级指针接收,

原来的链表:

插入之后 

代码:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLBuyNode(x);
	SLTNode* p = *pphead;
	//空链表
	if (*pphead==NULL)
	{
		*pphead = newNode;
	}
	else {
		//找尾节点
		while (p->next!=NULL)
		{
			p = p->next;
		}
		p->next = newNode;
	}

}

再来实现我们的删除操作:头删和尾删

头删:

我们的头删和我们的头插刚刚相反,是在我们的头部删除。

我们的删除不是简简单单的的改变phead指针的位置,我们需要量将我们节点空间释放,否则会导致内存泄露,因为你向我们的我们的栈区申请的,如果我们的申请了而不将我们的空间还给我们的系统,我们的空间是有限的,当我们很多申请的空间都没有归还,那我们的可能会出现一系列严重的问题。

所以我们在改变我们头指针的位置是还需要将我们删除的那个节点空间释放。

当我们的链表为空时,我们不能进行删除操作,需要断言一下,

代码:

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		SLTNode* p = (*pphead)->next;;
		free(*pphead);
		*pphead = p;
	}
}

尾删:

尾删和我们的尾插相反,是删除我们的尾部节点,他和我们的尾插一样,需要去寻找我们的尾部节点。

当我们链表为空时我们不可以去删除,我们在这里需要断言一下。

代码

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//空链表
	SLTNode* p = *pphead;
	if ((*pphead)->next == NULL)//只有一个元素
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//找到尾部
		while (p->next->next != NULL)
		{
			p = p->next;
		}
		free(p->next);
		p->next = NULL;
	}
	
}

查找元素

我们的查找元素很简单,我们只需要去遍历我们的链表,去和我们要找的的元素进行比较,如果相等我们就返回这个节点的地址,如果遍历完整个链表还没有找到这个与之相等的节点,就说明链表中没有该节点,此时我们返回空(NULL),在这之前我们的进行判断我们的链表为不为空。

代码:

//查找(按照元素查找)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* p = phead;
	if (phead==NULL)
	{
		printf("该链表中没有元素\n");
		exit(0);
	}
	else {
		//找元素
		while (p != NULL && p->data != x)
    	{
			p = p->next;
		}
		if (p == NULL)
		{
			return NULL;
		}
		else {
			return p;
		}
	}
	
}

打印链表中的元素

我们打印链表中的元素也是将我们的链表遍历,叫我们的节点中的数据域的值给打印出来,当然我们在遍历之前也要判断一下链表尾部为空。

代码

//打印元素
void SLTPrint(SLTNode* phead)
{
	SLTNode* p = phead;
	while (p)
	{
		printf("%d->", p->data);
		p = p->next;
	}
	printf("NULL\n");
}

在特点的节点插入和删除:

我们在指定的节点删除和插入,我们需要先在我们的链表中寻找一下有没有该节点,找到该节点后,如果我们是插入,我们只需要创建一个新的节点,我们先要将我们的新节点的next指针改成我们找到的节点的next,再去改变我们该节点的next指针,改成我们插入节点的指针,

对于删除,我们需要找到我们目标节点的前一个节点,因为我们的链表是单向的,当我们找到我们目标节点,但是我们却找不到他的前一个节点,所以我们是要找到目标节点的前一个节点,我们需要一个新的指针来帮我们记住我们需要删除节点,再来将我们目标节点的前一个节点的next改变成我们的目标节点的下一个节点,在将我们的目标节点的空间释放。

代码:

//查找(按照元素查找)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* p = phead;
	if (phead==NULL)
	{
		printf("该链表中没有元素\n");
		exit(0);
	}
	else {
		//找元素
		while (p != NULL && p->data != x)
    	{
			p = p->next;
		}
		if (p == NULL)
		{
			return NULL;
		}
		else {
			return p;
		}
	}
	
}

总代码:

我们需要将我们的文件管理好:我们的头文件和函数的声明就用一个头文件ListNode.h存放,我们实现的接口就用一个ListNode.c文件来存放。

ListNode.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include"Contact.h"
typedef struct person SLTDataType;
typedef struct SListNode
{
	//
	SLTDataType data;//放数据
	struct SListNode* next; //指向下一个节点
}SLTNode;

//打印
void SLTPrint(SLTNode* phead);
SLTNode* SLBuyNode(SLTDataType x);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

ListNode.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"SLQ.h"

//打印元素
void SLTPrint(SLTNode* phead)
{
	SLTNode* p = phead;
	while (p)
	{
		printf("%d->", p->data);
		p = p->next;
	}
	printf("NULL\n");
}

//申请新的节点
SLTNode* SLBuyNode(SLTDataType x)
{
	SLTNode* newNode = (SLTNode*)malloc (sizeof(SLTNode));
	if (newNode==NULL)
	{
		perror("malloc:");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

//尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLBuyNode(x);
	SLTNode* p = *pphead;
	//空链表
	if (*pphead==NULL)
	{
		*pphead = newNode;
	}
	else {
		//找尾节点
		while (p->next!=NULL)
		{
			p = p->next;
		}
		p->next = newNode;
	}

}
//销毁
void SListDesTroy(SLTNode** pphead)
{
	SLTNode* L = *pphead;
	while (*pphead)
	{
		L = (*pphead)->next;
		free(*pphead);
		*pphead = L;
	}
}
//头部插入删除 
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = NULL;
	newNode = SLBuyNode(x);
	newNode->next = *pphead;
	*pphead= newNode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//空链表
	SLTNode* p = *pphead;
	if ((*pphead)->next == NULL)//只有一个元素
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//找到尾部
		while (p->next->next != NULL)
		{
			p = p->next;
		}
		free(p->next);
		p->next = NULL;
	}
	
}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		SLTNode* p = (*pphead)->next;;
		free(*pphead);
		*pphead = p;
	}
}
//查找(按照元素查找)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* p = phead;
	if (phead==NULL)
	{
		printf("该链表中没有元素\n");
		exit(0);
	}
	else {
		//找元素
		while (p != NULL && p->data != x)
    	{
			p = p->next;
		}
		if (p == NULL)
		{
			return NULL;
		}
		else {
			return p;
		}
	}
	
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(*pphead);//空链表的时候
	assert(pos);
	SLTNode* newNode = SLBuyNode(x);
	SLTNode* p = *pphead;
	if (pos == p)//说明在是头插也可以使用我们的头插函数(我这里未使用,自己写)
	{
		newNode->next = *pphead;
		*pphead = newNode;
	}
	else {
		while (p->next != pos)
		{
			p = p->next;
		}
		newNode->next = p->next;
		p->next = newNode;
	}


}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos && *pphead);
	SLTNode* p = *pphead;
	if (*pphead == pos)//头删
	{
		*pphead = (*pphead)->next;
		free(pos);
	}
	
	else {
		while (p->next != pos)
		{
			p = p->next;
		}
		p->next =pos->next;
		free(pos);
		pos = NULL;
	}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode*newNode=SLBuyNode(x);//增加新节点
	newNode->next = pos->next;
	pos->next = newNode;
	
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode * pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		;
	}
	else {
		SLTNode* p = pos->next;
		pos->next = p->next;
		free(p);
		p = NULL;
	}
}

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

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

相关文章

web前端学习笔记10

10. CSS3基础 10.1 圆角 CSS3可以设置边框的圆角,其属性是border-radius,可以通过圆角属性制作出各种形状的图形和圆角效果。10.1.1 圆角 border-radius的四个属性值按顺时针排列,对应四个不同的圆角 案例代码 <!DOCTYPE html> <html lang="en"><…

杰发科技AC7801——ADC之Bandgap和内部温度计算

0. 参考 电流模架构Bandgap设计与仿真 bandgap的理解&#xff08;内部带隙电压基准&#xff09; ​ ​ 虽然看不懂这些公式&#xff0c;但是比较重要的一句应该是这个&#xff1a;因为传统带隙基准的输出值为1.2V ​ 1. 使用 参考示例代码。 40002000是falsh控制器寄…

Vue3专栏项目 -- 三、使用vue-router 和 vuex(上)

前面我们开发了两个页面的组件&#xff0c;现在我们需要把它们分成几个页面了&#xff0c;那么一个网页多个页面我们都熟悉&#xff0c;针对不同的url渲染不同的html静态页面&#xff0c;这是web世界的基本工作方式。 有时候我们点击一个东西&#xff0c;地址栏的路由跳转&…

DSP ARM FPGA 实验箱_音频处理_滤波操作教程:3-9 音频信号的滤波实验

一、实验目的 掌握Matlab辅助设计滤波器系数的方法&#xff0c;并实现音频混噪及IIR滤波器滤除&#xff0c;并在LCD上显示音频信号的FFT计算结果。 二、实验原理 音频接口采用的是24.576MHz&#xff08;读兆赫兹&#xff09;晶振&#xff0c;实验板上共有3个音频端口&#x…

JavaScript基础(六)

break & continue continue跳出本次循环&#xff0c;继续下面的循环。 break跳出终止循环。 写个简单的例子: <script> for (var i1; i<5; i){ if (i3){ continue; } console.log(i); } </script> 结果就是跳过i等于3的那次循环&#xff0c;而break: f…

大势所趋!企业网站HTTPS升级全面普及化

JoySSL官网 注册码230918 HTTPS加密协议的应用无疑是维护网络信息安全的重要一环。随着技术的不断进步与用户隐私意识的增强&#xff0c;HTTPS加密已不再仅仅是大型企业的专属&#xff0c;而是逐渐成为所有企业网站的标准配置&#xff0c;其普及化趋势显而易见&#xff0c;堪称…

人工智能|深度学习——PlotNeuralNet简单教程

一、简介 PlotNeuralNet是一个强大的开源Python库,它专为简化和美化神经网络图的绘制而设计 二、安装 需要下载的工具包括&#xff1a;MikTeX&#xff0c;Python代码编辑器&#xff08;这个肯定会有的吧&#xff09;&#xff0c;Git bash&#xff08;可选&#xff09;&#xff…

惠海 H6391 升压恒压芯片IC 2.6-5V升12V/18V方案 内置MOS 高效率 低功耗

升压恒压芯片IC的工作原理主要基于电感和电容的存储能量特性&#xff0c;以及脉宽调制&#xff08;PWM&#xff09;技术。在升压过程中&#xff0c;芯片内部包含了如输入滤波电容、续流二极管、升压电感、开关管、输出滤波电容等部分。当开关管处于导通状态时&#xff0c;电感中…

牛客小白月赛93

B交换数字 题目&#xff1a; 思路&#xff1a;我们可以知道&#xff0c;a*b% mod (a%mod) * (b%mod) 代码&#xff1a; void solve(){int n;cin >> n;string a, b;cin >> a >> b;for(int i 0;i < n;i )if(a[i] > b[i])swap(a[i], b[i]);int num1…

[Algorithm][递归][斐波那契数列模型][第N个泰波那契数][三步问题][使用最小花费爬楼][解码方法]详细讲解

目录 1.第 N 个泰波那契数1.题目链接2.算法原理详解3.代码实现 2.三步问题1.题目链接2.算法原理详解3.代码实现 3.使用最小花费爬楼梯1.题目链接2.算法原理详解3.代码实现 4.解码方法1.题目链接2.算法原理详解3.代码实现 1.第 N 个泰波那契数 1.题目链接 第 N 个泰波那契数 2…

mysql中sql语句 exists 判断子句的用法

如果子查询成立才执行父查询 exists判断子查询的使用例子&#xff1a; 张三不存在所以前面的父查询不执行 后面的子句结果存在&#xff0c;所以前面的父查询被执行 where条件所连接的嵌套子查询都是&#xff0c;条件子查询 ———————————————————————…

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果对一些基础理论感兴趣可以看这一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目录 1、Mybatis中参数传递问题 1.1 单个普通参数 1.2 多个普通参数 1.3 命名参数 1.4 POJO参数 1.5 Map参数 1.6 Collection|List|Array等参数 2、Mybatis参数传递【#与…

数据结构与算法学习笔记八-二叉树的顺序存储表示法和实现(C语言)

目录 前言 1.数组和结构体相关的一些知识 1.数组 2.结构体数组 3.递归遍历数组 2.二叉树的顺序存储表示法和实现 1.定义 2.初始化 3.先序遍历二叉树 4.中序遍历二叉树 5.后序遍历二叉树 6.完整代码 前言 二叉树的非递归的表示和实现。 1.数组和结构体相关的一些知…

Ps 滤镜:蒙尘与划痕

Ps菜单&#xff1a;滤镜/杂色/蒙尘与划痕 Filter/Noise/Dust & Scratch 蒙尘与划痕 Dust & Scratch滤镜可用于修复图像中的小瑕疵、尘埃或划痕&#xff0c;特别适合用于清理扫描的照片或老照片中的损伤&#xff0c;以及其他因拍摄条件不理想或相机传感器上的尘埃所造成…

网络安全防护:抵御DDoS和CC攻击

在当今数字化时代&#xff0c;网络安全已成为任何组织或个人不可忽视的重要议题。DDoS&#xff08;分布式拒绝服务&#xff09;攻击和CC&#xff08;命令与控制&#xff09;攻击作为两种最为常见的网络攻击方式&#xff0c;给网络运营者和用户带来了巨大的威胁和影响。本文将介…

Acrobat Pro DC 2023 for Mac:PDF处理的终极解决方案

Acrobat Pro DC 2023 for Mac为Mac用户提供了PDF处理的终极解决方案。它具备强大的文档处理能力&#xff0c;无论是查看、编辑还是创建PDF文件&#xff0c;都能轻松胜任。在编辑功能方面&#xff0c;Acrobat Pro DC 2023支持对文本、图像进行精准的修改和调整&#xff0c;还能添…

回溯法、全排列、子集等

回溯法 感想&#xff1a;回溯算法本质是一个循环&#xff0c;有点像while循环 一些回溯法&#xff08;递归&#xff09;的经典应用 1.全排列 2.子集 其实上面两个点&#xff0c;也是对应着高中数学里面的“排列”与“组合” 1.全排列问题 给定一个集合S{a,b,c}&#xff0…

服务的war包已经丢在tomcat中但是还是没法访问,如何排查?

问题出现的现象是我已经将 XWiki 的 WAR 包放置在 Tomcat 的 webapps目录下但仍然无法访问&#xff0c;反思之后可以从下面以下几个方面来诊断和解决问题&#xff1a; 1. 确认 Tomcat 正在运行 首先&#xff0c;确保 Tomcat 服务正在正常运行。可以使用以下命令检查 Tomcat 的…

word转pdf的java实现(documents4j)

一、多余的话 java实现word转pdf可用的jar包不多&#xff0c;很多都是收费的。最近发现com.documents4j挺好用的&#xff0c;它支持在本机转换&#xff0c;也支持远程服务转换。但它依赖于微软的office。电脑需要安装office才能转换。鉴于没在linux中使用office&#xff0c;本…

字符以及字符串函数

字符以及字符串函数 求字符串长度strlen 长度不受限制的字符串函数strcpystrcatstrcmp 长度受限制的字符串函数strncpystrncatstrncmp 字符串查找strstrstrtok 错误信息报告strerror 字符分类函数字符转换函数tolowertoupper 内存操作函数memcpymemmovememcmpmemset 这篇文章注…