单链表简单实现

news2024/11/27 12:17:44

单链表实现

  • 一、为什么会存在单链表?
  • 二、什么是单链表?
  • 三、单链表结构定义
  • 四、单链表的基本操作
    • 1、 创建结点
    • 2、 销毁链表
    • 3、 打印链表
    • 4、 尾插节点
    • 5、 头插结点
    • 6、 尾结点的删除
    • 7、 头结点的删除
    • 8、 单链表的查找
    • 9、 单链表在pos位置之后插入
    • 10、单链表在pos位置之后删除
  • 五、链表功能实现汇总

一、为什么会存在单链表?

前文我们介绍了顺序表,可以知道顺序表并不是完美的,也是有有一些缺陷的:

比如:
1、顺序表的头插、头删、中间插入、删除元素的操作所花费的时间开销很大,时间复杂度都是O(N^2);
2、动态顺序表尽管已经尽可能的减少空间开销了,但是还是存在一定的空间开销,并不能保证所开辟的空间能够完全被利用;
3、空间增容是有时间消耗的,拷贝数据,释放旧空间。会有不小的消耗;

为此为了解决这些问题,单链表就应运而生了;

二、什么是单链表?

单链表逻辑图

在这里插入图片描述
在这里插入图片描述

单链表同顺序表一样,都是线性表,除了首尾节点之外,任何一个节点都有自己的前驱和后继;但是单链表每个节点在物理上的地址是不连续的或者说是随机的,也就是说单链表的节点是要一个就开一个,不会过多的开辟空间;这一点正是弥补了顺序表的不足;

三、单链表结构定义

从上面的逻辑图我们就可以看出,每个节点的类型应该是一样的,节点主要分为数据域和指针域,同时对于一串链表来所,最后一个节点的指针域一定是NULL,以此来标记这是链表的结尾;同时我们只需要知道单链表的头节点,就可以很好的管理整个链表;
同时我们给出结点类型:
在这里插入图片描述

四、单链表的基本操作

与顺序表一样具备增删查改的基本功能:
在这里插入图片描述
我们先实现一些简单的操作:

1、 创建结点

我们前文说了管理一个链表需要一个头节点的指针就可以了;
为此我们可以专门设置一个SListNode*head=NULL;
来维护我们的单链表,该指针就保存头节点的指针就行了;
当然我们能进行这些操作,我们得先写一个函数来进行创建节点的操作,只有有了节点,我们才能开展后续操作:

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)//我们可以将数据在创建节点的时候就存入结点
{
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
	if (!tmp)//节点开辟失败,程序直接结束
		exit(-1);
	tmp->val = x;
	tmp->next = NULL;
	return tmp;
}

2、 销毁链表

我们的节点都是从堆区开辟的,当我们不在使用单链表时,我们应该将单链表销毁(也就是将所开辟的每个节点进行释放,否则容易找出内存泄漏);
思路:在这里插入图片描述
当然我们要考虑一下特殊情况,看看我们的思路能不能解决特殊情况,假设我是个空表,我的逻辑能不呢处理?
表为NULL,按照刚才逻辑,cur==NULL,cur都为NULL不会进入循环,也就是说代码什么也不会左,不会造成对NULL指针释放(对NULL释放也没事),也就是说,我们刚才的逻辑适合,无需对空表特殊处理;
代码实现:

// 单链表的销毁
void SListDestroy(SListNode* plist)
{
	SListNode* cur = plist;
	SListNode* next = NULL;
	while (cur)//空表不会进入循环
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}

3、 打印链表

打印链表和销毁链表的逻辑是一样的,只要cur初始化为头节点,只要cur不等于NULL,我们就打印cur所指结点的指针域,然后cur往后走,当cur走到NULL的时候,我们就不用打印了,就可以结束打印了,同时也不需要对空表特殊处理:
代码实现:

// 单链表打印
void SListPrint(const SListNode* plist)//我们最好加上const修饰,这样更严谨;因为我们指向读取结点里面的值,并不想修改
{
	const SListNode* cur = plist;
	while (cur)
	{
		printf("%d->",cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

4、 尾插节点

尾插结点故名思意就是在链表的尾部加入一个结点,然后再把链表的结点串起来,得到一个新链表;
当然我们要在尾部插入,就得找到最后一个结点对吧,最后一个结点有什么特点?
是不是最后一个结点的指针域为NULL啊,我们只要找到某个结点的指针域为NULL的就是最后一个结点,按照此思路,我们画图理解:
画图演示:
在这里插入图片描述
最后我们将开辟好的以初始化好的新节点直接连在最后一个结点末尾即可;
至此我们需要考虑一下特殊情况,假设传过来的是空表?
cur=NULL;
我们在判断cur->next等不等于NULL时会对NULL解引用,会报错,
这是编译器不能容忍的;
因此我们需要对空表这种情况需要单独处理;
为什么参数要设计成二级指针?

当然这两种情况混合在一起的话,我的链表的头指针就会发生变化;我们上面不是说了嘛,我们利用一个SListNode*head=NULL;head变量来维护整个链表,既然我们会该表head变量里面的值,我们是不是就需要传其地址才可以(才可以在另一个作用域,操作head变量里面的内容,可以这么理解head也是一个变量,也是在栈上开辟的,因此也是一块空间,只不过这块空间的名字叫做head,我们现在想在另一个作用域对该作用域下的head进行更改内容的操作,只能传这个空间的地址,只有这样,我们在另一个作用域的改变才能影响到head的取值,这也是为什么函数参数要设计成二级指针的原因),又由于head本身就是一个一级指针变量,我们对一级指针变量取地址,就再次得到一个地址,这个地址叫做二级指针,因此我们在参数设计时,应当串以二级指针;

代码实现:

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);//为了避免使用者乱传,先断言一下
	SListNode* cur = *pplist;
	SListNode* NewNode = BuySListNode(x);
	if (cur)//表不为NULL
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = NewNode;
	}
	else//空表
	{
		*pplist = NewNode;
	}
}

5、 头插结点

顾名思义,就是在头部插入结点,因此我们的头指针也会发生改表,为了便于管理,我们参数也应该设计成二级指针;
思路分析:cur表示没插入之前的头节点,NewNode表示新结点,那么只需把新节点的指针域,保存一下上一次的头节点cur,就完成了链表的头部插入,最后在更新一下头节点就完成了;
同时考虑一下NULL表能不能实现?
事实上空表,按照这个逻辑也是OK的(读者可以自行验证)为此我们无需对空表进行特殊处理;
代码实现:

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	SListNode* cur = *pplist;
	SListNode* NewNode = BuySListNode(x);
	NewNode->next = cur;
	*pplist = NewNode;
}

6、 尾结点的删除

上面讲了头节点的插入和尾结点的插入,接下来我们来讲讲尾结点的删除;
首先先讲解尾结点的删除:
首先想要删除尾结点,我们得首先知道尾结点,其次还得知道尾结点的下一个结点,这个好说,只要找到了尾结点,就找到了尾结点的下一个,关键是我们最后想把链表链接起来就得知道尾结点的上一个结点,为此我们得定义一个prev指针来记录尾结点的前一个结点;
同时cur找尾,cur找尾与尾插思想基本一直,只不过多了一步prev=cur
当然我们能删除结点的前提条件就是表不为NULL,如果表都为空了,那还删个屁,直接给你报错提醒;
所以我们最好断言一下链表是否为空!!!

画图理解:

在这里插入图片描述
当然我们考虑一下特殊情况,只有一个结点时是否能狗满足我们上述的逻辑?
在这里插入图片描述
代码实现:


// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);//链表为NULL就不要在删除了
	SListNode* cur = *pplist;
	SListNode* prev = NULL;
	if (!cur->next)//只有一个节点需要单独处理
	{
		free(cur);
		*pplist = NULL;
	}
	else
	{
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

7、 头结点的删除

也就是删除第一个结点,我们的头指针肯定该表,为了便于维护该链表,我们的参数也应该使用二级指针;
当然删除操作的前提条件就是要有的删,对于空表来说,我们删个毛!!
为此我们最好断言一下;
头节点的删除比较简单,我们可以先根据现有的头结点,找到它的下一个结点next,然后再释放掉头节点,就完成了头节点的删除操作,最后再将新的头节点跟新一下,我们不就完美实现了头节点的删除操作!!
画图:
在这里插入图片描述
当然我们依旧考虑一下特殊情况,只有一个结点的时候,上述逻辑是否能正常完成:
显然是可以的:
在这里插入图片描述

代码实现:

// 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	SListNode* cur = *pplist;
	assert(cur);//链表为NULL就不再删除了
	*pplist = cur->next;
	free(cur);
}

8、 单链表的查找

对于查找功能来说,我们并不需要更改链表里面的任何内容,我们只会读取数据,更不会修改头节点,因此此我们函数参数设计时就不必传二级指针了,就只需要一级指针就可以了,当然为了严谨,最好加上const修饰;
我们查找的思路也很简单,就是遍历链表,一个一个比较,只要找到了等于目标值的就返回该节点的指针;
在这里插入图片描述
当然如果链表都遍历完了都没找到的话,我们就返回NULL,表示没找到;
代码实现:

// 单链表查找
SListNode* SListFind(const SListNode* plist, SLTDateType x)
{
	assert(plist);//链表为空就不在找了
	const SListNode* cur = plist;
	while (cur)
	{
		if (cur->val == x)
			break;
		else
			cur = cur->next;
	}
	return (SListNode*)cur;//避免返回类型不一致,抱警告
}

9、 单链表在pos位置之后插入

也就是说现在我给你一个合法的结点指针(保证该指针再链表中存在),然后你在该结点的后面加一个指针;这不简单?
现在我们通过pos可以知道它的下一个结点next(next=pos->next),我们就把新节点插在pos位置后面,然后再把next插在next前面就行了:
画图:
在这里插入图片描述

如果pos位置就在表尾?
那么next=NULL;
pos->next=NewNode;
NewNode->next=next;
代码还是跑的通;
当然如果pos为NULL的话有两种处理方式:
1、直接尾插;
2、直接报警告,因为我是再pos的后面插入,我怎么能再NULL后面插入呢?
本文采用第二种;
代码实现:

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);//NULL指针可不能链接下一个数据;
	SListNode* next = pos->next;
	SListNode* NewNode = BuySListNode(x);
	pos->next = NewNode;
	NewNode->next = next;
}

现在我们可以思考一个问题?
为什么不在pos位置之前插入?

再pos之后插入,操作更简单;函数参数太少,由于这是单链表,我们无法知道pos的前一个节点,就无法将新节点在pos节点和pos前一个节点之间链接起来;
那如果我现在给定链表的头指针,那么我们又该怎么做?
当然既然要在pos前面解决我们就必须知道pos的前一个指针;

还是画图解决:
在这里插入图片描述
这是大概思路,当然我们需要
考虑一下特殊情况:
1、表为NULL的是时候,prev=NULL;cur=NULL;直接进行尾插或头插;
2、pos刚好为头指针的时候;prev=NULL;cur=pos;直接进行头插;这时候头节点会被改变,所以我们的函数参数应该设计成二级指针;
在这里插入图片描述
综上述两种情况,我们建议直接使用头插;
这便是再pos之前插入的大概思路,读者可以自己研究一下代码如何写?

10、单链表在pos位置之后删除

与pos位置之后插入一个道理,我们能轻松找到pos下一个结点;
(我们同样保证pos合法)
在这里插入图片描述
考虑一下特殊情况,pos刚好等于尾指针?
是不是就没有东西可删了,直接报警告就可以了;
代码实现:

void SListEraseAfter(SListNode* pos)
{
	assert(pos);//不要对NULL指针操作;
	assert(pos->next);//如果pos是尾节点,就没东西可删
	SListNode* Next = pos->next->next;
	free(pos->next);
	pos->next = Next;
}

还是同样的问题:

为什么不删除pos位置?
理由和上一个pos位置之前插入一样;
那么我先在给你头结点嘞?又该如何操作?
那么我就需要取寻找pos的前前一个结点;
cur还是从头节点开始遍历,然后我们怕段cur->next->next与pos的关系:
在这里插入图片描述
考虑特殊情况:
1、pos为头节点,前面没有可删除的直接报警告;assert(*pplist!=pos);//我们必须传二级指针,因为我们头节点时可能被改变的;
在这里插入图片描述
2、pos为第二个结点,直接实施头删;if(*pplist->next==pos)
在这里插入图片描述

当然我们最先得保证链表有的删的;
(读者可以自行尝试写出相关代码😁😁)

五、链表功能实现汇总

函数、类型、变量、声明\头文件的包含:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType val;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);

// 单链表打印
void SListPrint(const SListNode* plist);

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删
void SListPopBack(SListNode** pplist);

// 单链表头删
void SListPopFront(SListNode** pplist);

// 单链表查找
SListNode* SListFind(const SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);

// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);

// 单链表的销毁
void SListDestroy ( SListNode* plist);

功能实现:

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
	if (!tmp)
		exit(-1);
	tmp->val = x;
	tmp->next = NULL;
	return tmp;
}

// 单链表打印
void SListPrint(const SListNode* plist)
{
	const SListNode* cur = plist;
	while (cur)
	{
		printf("%d->",cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

// 单链表的销毁
void SListDestroy(SListNode* plist)
{
	SListNode* cur = plist;
	SListNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	SListNode* cur = *pplist;
	SListNode* NewNode = BuySListNode(x);
	if (cur)
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = NewNode;
	}
	else
	{
		*pplist = NewNode;
	}
}

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	SListNode* cur = *pplist;
	SListNode* NewNode = BuySListNode(x);
	NewNode->next = cur;
	*pplist = NewNode;
}

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);//链表为NULL就不要在删除了
	SListNode* cur = *pplist;
	SListNode* prev = NULL;
	if (!cur->next)//只有一个节点需要单独处理
	{
		free(cur);
		*pplist = NULL;
	}
	else
	{
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

// 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	SListNode* cur = *pplist;
	assert(cur);//链表为NULL就不再删除了
	*pplist = cur->next;
	free(cur);
}
// 单链表查找
SListNode* SListFind(const SListNode* plist, SLTDateType x)
{
	assert(plist);//链表为空就不在找了
	const SListNode* cur = plist;
	while (cur)
	{
		if (cur->val == x)
			break;
		else
			cur = cur->next;
	}
	return (SListNode*)cur;
}

// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?参数太少,由于这是单链表,我们无法知道pos的前一个节点,就无法将新节点在pos节点和pos前一个节点之间链接起来
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);//NULL指针可不能链接下一个数据;
	SListNode* next = pos->next;
	SListNode* NewNode = BuySListNode(x);
	pos->next = NewNode;
	NewNode->next = next;
}

// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?参数太少
void SListEraseAfter(SListNode* pos)
{
	assert(pos);//不要对NULL指针操作;
	assert(pos->next);//如果pos是尾节点,就没东西可删
	SListNode* Next = pos->next->next;
	free(pos->next);
	pos->next = Next;
}

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

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

相关文章

在jenkins上创建一个CANoe Job

目录实战项目CANoe 工程配置全局安全创建 slave 节点创建pipline Job&#xff1a; CANoeAutoRun实战项目CANoe 工程 配置全局安全 将代理和SSH Server都设置成随机选取&#xff0c;后面再本机创建slave 节点要用&#xff0c;因为我们会在用一台机器上创建了master和slave节点…

快充伤电池?我来帮何同学做个假设检验

最近看到何同学的视频&#xff0c;拿40部手机花两年半做了关于各种充电的实验视频&#xff0c;视频确实很好看&#xff0c;花里胡哨&#xff0c;看着科技感满满&#xff5e;。但是关于实验设计和根据实验的数据得出最后的结论上似乎有些草率。 实验设计上就不提了&#xff0c;…

周涛:在大数据沙滩上捡拾“珍珠”|奋斗者正青春

“我始终觉得&#xff0c;创新的本原就是好奇心&#xff0c;要像小孩儿一样&#xff0c;一直不断地追问&#xff0c;向这个世界讨要答案。在追寻答案的过程中&#xff0c;要有独立探索和批评的精神&#xff0c;不能轻信权威。” 1 提起电子科技大学教授周涛&#xff0c;大多…

【定语从句练习题】who、which

1. 填空训练 翻译的时候加上 … 的 1.who 2.which 3.which 4.which 5.who 6.which 7.which 8.who 9.who 10.which 11.which 12.who 2. 选择 1.took 2.live 3.she is 3.lost 5.bought 6.is parked 7.it cuts 8.writes 9.make 10.lent you. 10.lend sb. sth 这里需要&…

Java反射06:反射的应用之动态代理

反射的应用之动态代理 &#xff08;这里没听懂&#xff0c;知道反射体现了代理动态性就行&#xff0c;后面框架再学习&#xff09; 代理设计模式的原理 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何…

C语言之指针详解

文章目录1 指针1.1 简介1.2 什么是指针1.3 使用指针1.3.1 简单使用1.3.2 NULL 指针1.3.3 指针算术运算1.3.3.1 定义1.3.3.2 遍历数组&#xff1a;递增一个指针1.3.3.3 遍历数组&#xff1a;递减一个指针1.3.3.4 指针的比较1.3.4 指针数组1.3.5 指向数组的指针1.3.6 指向指针的指…

Django中利用Admin后台实现Excel/CSV的导入更新数据库和导出数据到Excel/CSV

本文基于Django自带的admin 后台实现Excel&#xff0c;csv&#xff0c;Json等格式文件的导入并更新后台数据库。 核心是引入 django-import-export模块。 1、测试相数据准备&#xff1a; 我们先创建一个app&#xff1a;app01 python manage.py startapp app01 然后在app01…

软考下午题第1题——数据流,题目分析与案例解析:

答题技巧-【11-12分】分必拿方法&#xff1a; 下午第一题肯定是数据流的题目&#xff0c;那么&#xff0c;数据流肯定要找到对应的实体、关系模式等内容&#xff0c;审题的时候一定要细致&#xff0c;下午时间也是相当够的&#xff0c;所以每句话记住&#xff0c;至少读3遍&am…

【pyhon】利用pygame实现彩图版飞机大战(附源码 可供大作业练习使用)

源码请点赞关注收藏后评论区留言或私信博主 演示视频已上传到我的主页 有需要者可自行观看 演示视频如下&#xff1a; 飞机大战接下来先介绍一下游戏的玩法 在PyCharm中运行《彩图版飞机大战》即可进入如图1所示的游戏界面。 具体的操作步骤如下&#xff1a; &#xff08;1&…

Android Native APP开发笔记:多线程编程

文章目录目的Java中的多线程ThreadRunnableTimerAndroid中的多线程HandlerAsyncTask总结目的 Android中UI线程对于开发者和用户来说都是最主要接触到的线程。一般来说为了UI流畅、不卡顿&#xff0c;耗时操作是不推荐放在UI线程中的。但是耗时操作的需求又是存在的&#xff0c…

Spring Cloud(八):Spring Cloud Alibaba Seata 2PC、AT、XA、TCC

事务简介 分布式事务&#xff1a;https://www.processon.com/view/link/61cd52fb0e3e7441570801ab 本地事务 JDBC 事务模式 Connection conn ... //获取数据库连接 conn.setAutoCommit(false); //开启事务 try{//...执行增删改查sqlconn.commit(); //提交事务 }catch (Exce…

【C++学习】日期类和内存管理

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 日期类的实现和内存管理&#x1f3ec;日期类的实现&#x1f3ec;C/C内存分布&#x1f3ec;C内存管理方…

【工具】Git-码农“吃饭的碗”要拿好

汝之观览&#xff0c;吾之幸也&#xff01;本文主要讲解的是Git的轻巧使用&#xff08;创建、下载、上传、更新、回退&#xff09;&#xff0c;我们平常都是通过idea自带的git工具&#xff0c;或者其他工具来拉取提交代码&#xff0c;这里主要用命令行的方式拉取代码&#xff0…

基于springboot+vue的心理预约咨询测试交流小程序

&#x1f496;&#x1f496;作者&#xff1a;IT跃迁谷毕设展 &#x1f499;&#x1f499;个人简介&#xff1a;曾长期从事计算机专业培训教学&#xff0c;本人也热爱上课教学&#xff0c;语言擅长Java、微信小程序、Python、Golang、安卓Android等。平常会做一些项目定制化开发…

【REST系列】详解REST架构风格 —— 带你阅读Web发展史上的一个重要技术文献

文章目录REST详解词组解释论文摘要REST架构约束一、Client–server&#xff1a;客户端-服务器二、Stateless&#xff1a;无状态三、Cacheability&#xff1a;缓存四、⭐Uniform Interface&#xff1a;统一接口 (RESTful API)五、Layered System&#xff1a;分层系统六、Code-On…

荧光生物标记物510758-19-7,5-羧基荧光素-炔烃,5-FAM alkyne

5-FAM-Alkyne 是一种高选择性和灵敏的荧光生物标记物&#xff0c;可用于标记碱性磷酸酶 (ALP)。炔烃可以通过铜催化的点击化学与多种叠氮化合物共轭。&#xff08;西安凯新生物科技有限公司​所有的试剂仅用于科研实验&#xff0c;不可用于人体试验&#xff09; 5-FAM Alkyne …

【Hadoop】P2 Hadoop简介

Hadoop是什么 Hadoop为分布式系统基础框架。主要解决海量数据的存储和海量数据的分析计算问题。 大数据解决的是海量数据的采集、存储和计算。 Hadoop三大发行版本 Apache 最原始最基础的版本&#xff0c;2006年诞生&#xff0c;开源&#xff1b; Cloudera 内部封装Apache&am…

HTML中华传统文化题材网页《中国民间年画》HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Redis网络模型-IO多路复用

Redis网络模型-IO多路复用 系统IO交互 IO多路复用概念 文件描述符&#xff08;File Descriptor):简称FD&#xff0c;是一个从O开始递增的无符号整数&#xff0c;用来关联Linux中的一个文件。在Linux中&#xff0c;一切皆文件&#xff0c;例如常规文件、视频、硬件设备等&…

卷积神经网络的卷积层

文章目录卷积核正向传播反向传播参考文献附录卷积核 笔者在学会了如何运用卷积神经网路后&#xff0c;突然有一天萌发了很多问题&#xff0c;为什么要用卷积核&#xff1f;卷积核具体完成了什么工作&#xff1f;带着这些疑问&#xff0c;笔者开始查询资料&#xff0c;其中一段视…