数据结构——链表讲解(1)
作者:几冬雪来
时间:2023年3月3日
内容:数据结构链表讲解
目录
前言:
链表的概念:
1.为什么要有链表:
2.链表的运行原理:
3.链表的形态多少:
4.单链表的代码书写:
1.创建文件:
2.创建结构体:
3.链表的打印:
4.链表执行:
5.尾插:
6.头插:
7.尾删:
8.头删:
9.代码:
结尾:
前言:
在上一篇博客中我们结束了对顺序表的讲解,在讲解顺序表的过程中一个新的概念——线性表被提起,通过了解得知顺序表和链表都属于线性表的一种,而在之前我们讲解了顺序表的内容,那么今天就要讲解数据结构另一大重要的内容——链表。
链表的概念:
链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.为什么要有链表:
在之前有说过,顺序表和链表都属于线性表中的一种。那么为什么在学习完顺序表后还要再去学习链表呢?那肯定是因为链表相较于顺序表有它的优势所在,那么顺序表对比起链表有哪些劣势的地方存在,我们一一说明一下。
以上两条就是顺序表的缺点,这里有人可能不太了解空间浪费是什么意思, 举个例子来说明,正常情况下每当我们空间不够的时候都会进行扩容操作,而每次扩容我们不可能只增加一小块空间,通常扩容后的空间大小是我们原大小的一或者两倍。这里假设我们开辟了150个数据的空间,但是实际上我们要存的值只有101个,这里我们的空间就会浪费。这都是因为顺序表开辟的空间是连续的的特性。
2.链表的运行原理:
而链表则是一个数据存储在一个内存块,它不要求我们的内容连续,每个内存块都用指针进行相关的链接。而这些内存块在链表里面则被称为——结点。
这里如果想通过第一个数据找到第二个的值该怎么办?这里则将这里的第一个数据的后面存放下一个数据的地址,类似我们的锁链一样。
3.链表的形态多少:
和顺序表的插入还有细分为头插和尾插两种方式一样,我们的链表也足足有8种之多的形态。不过在数据结构的学习中,8种形态并不会全部讲解,我们会在这8种形式中挑2种十分常用且有代表性的形式来讲解,那么今天所讲解的就是我们其中的单链表。
4.单链表的代码书写:
既然已经对链表的概念有所了解,那么接下来我们就一点一点的写单链表的代码,并对其进行分析。
1.创建文件:
从上文可以得知,顺序表和链表都被归于线性表中,因此写顺序表代码的前置条件在写链表的时候也要使用。
这里依旧创建一个.h文件和两个.c文件。
2.创建结构体:
顺序表和链表本质是相似的,因此和顺序表一样凡是有多个数据,我们都应该将数据存储到我们的结构体当中去,那么在写链表的时候结构体里面要存放什么数据?
首先是像写顺序表代码一样,将int类型重命名。接下来书写链表结构体内的内容,一开始先定义一个数据,但是接下来定义的这个指针就有人看不懂了。这里在结构体中为什么突然多出来这个东西?
根据上文所讲,我们在第一个数据的后面存放下一个数据的地址。而我们每一个结点就是一个结构体,因此在这里我们存下一个结点的地址也就是存放一个结构体类型的指针。
有人这里就要说了:结构体里面再次调用结构体这种方法在C语言中不是不行的吗?这种方式确实不行,但是在这个结构体中,我们其实是存放了一个地址而并非结构体,只是刚好我们这个指针的类型是结构体类型罢了。所以这里并不会报错,然后在最后将结构体进行重命名就行了。
3.链表的打印:
在写链表代码的过程中,理解它也是我们应该做的一件事情,那么既然要理解单凭口头讲解肯定是不够彻底的,因此我们需要先将其代码写出来。
链表的打印可以说是链表讲解中最简单的一个部分,与顺序表的打印内容不同,链表的打印不需要多个数据什么的。
在打印的时候,在这里只需要一个指针指向我们的第一个结点。
在这里我们就遇到了链表的第一个问题,在打印链表的时候一开始需要对其进行断言吗?其实在写链表代码我们是不需要对其断言的。这里有人就可能要问了:不是说顺序表和链表都属于线性表。那么在顺序表中最基本的对指针进行断言的操作为什么在链表中却不用了?
首先,如果在一开始没有断言,这样可能导致顺序表或者链表为空。那么如果链表或者顺序表为空的话,我们能否去打印它们呢?
如果顺序表为空的话,其实还是可以将其打印出来,只不过里面没有数据。
接下来是空的链表,空链表也是可以打印出来的,一般我们链表都以空为结束。如果一开始进行断言的话,程序直接被终止掉了,这样是不合适的。
还有就是这两个结构其实是不一样的,链表的指针是指向第一个存有数据的节点,而我们的顺序表的指针指向的是结构体。哪怕顺序表中应该数据都没有,但是它的结构还是存在的,顺序表里有无数据是由size来决定的,但是如果这个结构体的指针都为空,size的大小就毫无意义了。
4.链表执行:
接下来继续写我们的代码。
大家能看得懂这段代码想表达的意思吗?我们来一步一步讲解一下。
一开始我们建立一个指针,然后把phead赋值给cur,这样子我们的cur也会指向第一个节点的位置,和顺序表的结束方法不同,链表是以空也就是NULL结束的,所以循环条件就应该是不为空。
接下来打印数据的话就像顺序表的那样进行指向,在打印完了一个值之后我们就要找下一个值的位置,那应该怎么走?这里就用cur指向next就可以指向下一个值的地址。
就像这里,cur一开始指向值的地址值,next以后指向结构体的下一个成员,这个成员存放着2的地址。所以cur经过next之后指向了2的地址,打印出来也就是2的值。到了最后cur指向4后下一个值为空我们循环结束。
当然这里的条件也可以进行修改。
5.尾插:
在写完了什么较简单的代码后,接下来就要向更深入的知识点前进了。
记得在我们书写顺序表的代码中,第一个执行的程序就是尾插数据,那么在书写链表的时候,第一个程序还是尾部插入。
这里还是一样,一开始先创建一个结点,但是在这里又和顺序表的结构不一样。因为链表的空间并不连续,因此并不用和顺序表一样进行扩容,链表的空间是随时都有的。然后检查我们有没有创建结点,接下来将要插入的结点进行初始化。
剩下的就是将这个新结点和上一个结点进行对接,严格来说就是找上一个结点的尾。
不为空链表的尾插
尾插的本质:原尾节点中要存储新尾结点的地址
但是在链接的过程中是有一个坑的,很多人都被坑到过,那是什么样的代码?我们将其写出来看看。
这么看来代码是没有问题的,一开始将phead赋值给同类型的tail,这样tail也指向第一个结点的地址,而后tail不为0,我们让tail指向结构体的第二个元素后赋值给tail,最后将指针变量newnode赋值给tail,看似没有问题,但是这里忽略了一个重要的因素。
这里我们的tail和newnode变量都是局部变量,出了作用域要被销毁。 因此这样链表并没有被链接起来,链表连接需要我们上一个结点存下一个结点的地址。那代码要怎么修改?
这里我们就把newnode的值也就是我们想添加的值的地址给了tail的next,链表链接其实改的是这个结构体。在上一个代码中,我们只是将一个指针赋值给另一个指针,而在这个代码中则是用next存放下一个结点的地址,二者是有差别的。
这里我们的尾插代码看似差不多完成了,但是其实这里还有问题的存在。如果这个链表为空,程序会发生什么?这里分两种情况。
第一种情况如果链表为空的话,这里直接将结点链接到它的后面。这样就可以打印我们的值了,真的是这样吗?这里输入几个然后打印出来看看会发生什么?
运行结果输出为空?很明显结果并不符合预期,是代码哪里写错了吗?我们将数据调试出来看看出了什么问题。
这里从窗口可以看见,一开始的plist为空,而后调用函数将指针plist进行传参,用phead来进行接收,代码往下走因为我们要插入一个值,所以要创建一个结点而结点是有它的地址的,后面进行判断,因为一开始是空链表所以phead为空符合条件,接下来再把newnode赋值给phead。看起来一切都合情合理,但是这个坑需要仔细寻找。
通过调试看出,在最后一步将newnode赋值给phead的代码执行了,phead的地址进行了修改,但是这里的plist依旧为空没有被修改。这就是这段代码的第二个坑。
为什么plist没有被改变?
这里就是C语言中一个最简单的结论:形参是实参的一份临时拷贝,形参的改变不影响实参。
类似这个代码我们对ptr的改变并不会影响到px,这里不是指针的问题,类似以前改写一个值,我们传的是整形类型后用整形指针接收地址,这样形参的改变就可以影响实参。这被称为传址调用,如果是用整形(同类型)来接收的话,就会变为传值调用。
而这个代码,我们要改变的值的类型是一个指针类型,传参后依旧用一个一级指针接收属于同类型,因此在这里ptr的改变也不会影响到px,那如果要影响到实参代码要如何修改?
像这样进行修改即可,要求传一级指针的话应该传一级指针的地址,而后用一个二级指针来接收这个地址,传递的是一级指针接收的是二级指针,pptr的改变才能影响到px。
因此也得出一个结论:
改变的int*,使用int*的地址,int**的指针来接收。
可能还是有人不懂程序是怎么运行的,我们可以画个图来理解一下。
这就是两种情况,第一种因为是同类型传参,所以p并没有指向那块空间的地址。第二种我们传一级指针用二级指针来接收,相当于px里面存放了要打印值的地址。
因此第一次方法,我们p中并没有被赋值,Func函数结束后空间销毁,p的值依旧为空。
第二种方法里,p中存放着想打印的值的地址,即使空间被销毁,px中依旧存在着这个值。
那么代码要进行修改。
这就是代码改后的样子,改进后运行起来试试看。
代码可以正常运行并输出我们想要的结果,这就证明我们的这个尾插的代码成功了。但是有眼尖的同学就提出他的问题。在前面刚刚说过,这里要用二级指针来改变值,但是在最后赋值的时候我们用了一级指针,这不就相冲突了吗?
首先要明白这里要改变的是什么?其实除了第一次赋值要使用到plish,在下面我们写代码的过程中已经不需要再用到了。这里改变的对象并不是指针,而是结构体,因此这里我们并不使用二级指针来改变。
接下来再问一个问题,在.h文件中SLPrint需不需要用二级指针来接收?答案是:不需要。因为这里我们并没有改变指针而是打印数据,使用一级指针够用。
那么这里我们头插的代码和需要了解的坑都讲解完毕了。
6.头插:
jiang讲解完了尾插,接下来就要讲解同源的头插。首先就是一个问题,头插的代码书写需不需要和尾插一样使用二级指针?这里是需要的。
这里写头插的代码也分为两种情况。我们来看看是分为哪两种情况?
一种情况就是链表一开始里面就有数据,另一种情况就是指针为空的情况。 开始还是先写代码,和尾插一样都是要先创立一个结点,而创建结点的方法在尾插的时候就已经书写过了。那么为了避免后面经常要书写创建结点的代码,我们可以将其进行封装,提取一个公共的函数。
既然提取了一个公共函数,那么尾插一开始创建结点的代码就能用函数来代替。同样的写头插的时候也可以引用这个函数。
接下来就来实现头插,那么头插数据是怎么样插入的呢?我们先将代码书写出来。
这就是我们头插的代码,一开始先创建一个结点并赋值给newnode,而后这个创建的结点进行赋值,因为*pphead指向plis的地址,也就是未修改前第一个结点的地址,我们将它赋值给newnode的next,最后因为第一个结点被改变了,因此这里需要将第一个结点改为newnode。
这个就是我们尾插的运行原理。
并且代码可以正常的运行,并输出指定的结果,那么到这里我们链表的两种插入方法头插和尾插就讲解完毕了。
7.尾删:
讲解忘了两种插入方法后,下来就是两种删除方法,这里我们先讲解两种方法中的——尾删。
首先,尾删需不需要二级指针呢?这个先放置一下,先书写代码。要进行尾删操作,首先就是要找到我们的尾。而在尾插的代码中曾经写过找尾的方法,这里我们就可以直接引用。
在写尾删的时候,很多人会写这种形式的代码,看起来是没有问题的。先进行找尾,找到了就将其释放掉。但是这样写了之后,好多人的程序跑不过去这又是为什么?我们试一试用现在这个代码来删除数据试试看。
这块可以看出来,如果我们用上面的代码去删除数据的话,程序会直接出错。那么为什么会报错呢?
在上面这个代码中,我们找到了尾结点,并将其释放掉。但是这么做导致了倒数第二个结点中的next指向了一个野指针,这里置为空也对其没有影响,因为这里我们只是对tail置空tail是我们的局部变量,并不能对next产生影响。那代码需要怎么样修改。
这就是我们代码修改之后的样子。这里我们新创建了一个指针并初始化为空,进入循环后,我们先将tail赋值给prev,然后tail指向下一个结点,到最后我们找到了尾后依旧将其释放置空,但是这里prev是指向倒数第二个结点,在这里将它的next置空就不会指向野指针了。
这里就成功的进行了尾删操作,同时我们这个尾删的代码也有另一种写法。
运用这种方法我们也可以实现想要的尾插操作,但是代码到这里就结束了吗?并没有,这里的代码依旧存在着问题。那么是哪里出问题了?
这里我们不断的进行尾删操作,一开始并没有问题,但是当链表中只存在一个结点的时候,问题就出现了。我们上面两种写法都会出错。
首先是我们的第二种方法的问题所在:
这个代码在判断阶段出了问题,因为链表只剩下一个结点,所以我们的tail->next就已经是空了,而后再在原来的基础上进行next是会出错的。
那第一种方法的错误又在哪里呢?
第一种方法的错误就出现在最后prev->next这里,在上面我们可以得知,prev一开始被我们置为空,当只剩下一个结点的时候循环不会进行,我们的prev依旧为空,最后还要将prev->next置为空,这就是第一种方法的问题所在。
当然这只是链表只剩下一个结点的情况,如果链表没有结点的话,这两种方法出现的问题会更加的多,比如第一种方法中循环不会执行。
那么为了避免这种情况的发生我们该怎么样对代码进行修改?
这里就需要对其判断,如果第一个结点的next为空的话,我们就直接将其释放后置空。
还有一种情况,那就是一开始链表就为空,这种情况又要怎么去处理它呢?
在开始的时候就要对代码进行判断,如果链表一开始为空的话,这里就直接return结束又或者是将其进行断言。
8.头删:
接下来就是我们的头删,头插相较于尾插就比较简单了,为什么?
按照惯例,我们还是先将头删的代码写出来。
首先检查链表是不是空链表,无论是头删还是尾删,链表内有数据才可以让我们删除,因此这一步是必不可少的。
但是和尾删的代码不一样,头删的操作并不用对只有一个结点的情况进行另外的代码书写。
那么指针头插操作是怎么样运行的?
这就是头删的原理,将我们链表中的头结点删除,而后链表的指针指向原理第二个结点的地址,这样就是我们的头删操作。
接下来就是书写我们的代码了。
而后我们将代码运行起来看看结果是什么?
从这张图片来看,我们的头删的操作可以运行起来,并且没有报错。
9.代码:
SList.h文件
#include "SLish.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL)//while(cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead,SLTDataType x)
{/*SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;*/SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{/*assert(*pphead);*/if (*pphead == NULL){return;}if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}//while(tail->next->next!=NULL)//{// tail = tail->next;//}//free(tail->next);//tail->next = NULL;free(tail);tail = NULL;prev->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{/*assert(*pphead);*/if (*pphead == NULL){return;}SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}
test.c文件
#include "SLish.h"//void TestSList1()
//{
// SLTNode* plist = NULL;
// SLTPushBack(&plist, 1);
// SLTPushBack(&plist, 2);
// SLTPushBack(&plist, 3);
// SLTPushBack(&plist, 4);
//
// SLTPrint(plist);
//}//void TestSList2()
//{
// SLTNode* plist = NULL;
// SLTPushFront(&plist, 1);
// SLTPushFront(&plist, 2);
// SLTPushFront(&plist, 3);
// SLTPushFront(&plist, 4);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//
// SLTPopBack(&plist);
// SLTPrint(plist);
//}void TestSList3()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);
}int main()
{/*TestSList1();*//*TestSList2();*/TestSList3();return 0;
}
SList.h文件
#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead);
void SLTPushBack(SLTNode** pphead,SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
结尾:
到这里我们链表的头插头删尾插尾删等程序代码就已经写完了,但是和顺序表一样,链表也有在中间插入或者删除的操作,这些操作我们放在下一篇博客中去讲解,这篇博客相较与以往的博客,内容有质的提升,所以我们要先搞懂头删这些程序的原理不要操之过急。因为内容质量的提升,我在有些地方自己理解了,但是没有办法完全表达出来,这一个点还请各位读者见谅。最后希望这篇博客能帮到学习链表的同学。
相关文章:

数据结构——链表讲解(1)
作者:几冬雪来 时间:2023年3月3日 内容:数据结构链表讲解 目录 前言: 链表的概念: 1.为什么要有链表: 2.链表的运行原理: 3.链表的形态多少: 4.单链表的代码书写࿱…...

docker部署MySQL主从服务
一.主从同步流程关于MySQL主从复制主要同步的是binlog日志,涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在从节点,如下图所示:当主库数据发生变更时࿰…...

儿童护目台灯哪种好用?几款真的保护视力的台灯品牌推荐
儿童眼睛还未发育完全,眼睛比较脆弱,但是现在的小孩子学习任务也比较繁重,经常晚上看书写字,所以选择合适的护眼台灯来保护眼睛很重要。 选择儿童护目台灯需要注意以下几个方面: (一)色温和亮…...
游戏逆向基础之OD找CALL实践
在逆向中除了分析数据之外,另外一个重要的工作就是找算法,找CALL 例如各种功能函数:攻击CALL,走路CALL,喊话CALL等等 以及加密解密等算法需要我们先锁定其位置,然后进行逆向分析。 最常见方法一 API函数下断,例如send …...

File 文件操作
File 文件操作: 一、常用方法: 方法类型描述public File(String pathname)构造给定一个要操作文件的完整路径public File(File parent, String child)构造给定要操作文件的父路径和子文件名称public boolean createNewFile() throws IOExce…...

QT基础(18)- QAbstractSocket
QT基础(18)- QAbstractSocket1 创建简单的客户端2 QAbstractSocket2.1 简介2.2 枚举2.2.1 BingFlag2.2.2 NetworkLayerProtocol2.2.3 PauseMode2.2.4 SocketError2.2.5 SocketOption2.2.6 SocketType2.2.7 SocketState2.3 公有函数2.3.1 构造函数2.3.2 a…...

机器学习与目标检测作业:安装pytorch
机器学习与目标检测作业:安装pytorch一、 进入官网复制下载命令二、 下载的过程2.1 conda命令运行三、 测试pytorch是否安装成功安装pytorch教程 一、 进入官网复制下载命令 进入官网复制下载命令如下图所示 二、 下载的过程 下载的过程如下图所示 2.1 conda命令运…...

Android 源码中的 JNI,到底是如何使用的?
Linux下 JNI的使用学习 Android 其中涉及对 JNI 的使用;JNI的使用对于 Android 来说又是十分的重要和关键。那么到底 Java 到底是如何调用 C/C 的,下面是非常简单的计算器源码,只是用来熟悉JNI的基本语法,其中我自己碰到过的一个问…...

重磅新品 / 酷炫展品 / 强大生态,广和通玩转 MWC Barcelona 2023
2月27日,2023世界移动通信大会(MWC Barcelona 2023)在西班牙巴塞罗那正式开幕。全球知名移动运营商、设备制造商、技术提供商、物联网企业齐聚一堂,以领先的技术、创新的场景、前瞻的洞察向全行业输送最新鲜的行业观点。作为全球领…...

Hbuilder+uniapp 从零开始创建一个小程序
当你看到这篇博客的时候,那~说明~我的这篇博客写完了……哈哈哈哈哈哈哈哈。好的,清耐心往下看哈。如果有需要的,可以关注一下小作,后面还有小程序的云开发嗷~一、申请一个小程序账号(已经有账号的小可爱可以跳过&…...
亚商投资顾问早餐FM/0303支持新能源汽车消费
01/亚商投资顾问早间导读高层调研集成电路企业并主持召开座谈会商务部:今年将积极出台新政策措施支持新能源汽车消费商务部:推动农村消费进一步恢复和扩大更好助力乡村振兴干细胞应用接连获重大突破机构密集调研相关上市公司02/亚商投资顾问新闻早餐// 热…...

Spring Boot 整合分布式缓存 Memcached
Memcached是一个开源、高性能,将数据分布于内存中并使用key-value存储结构的缓存系统。它通过在内存中缓存数据来减少向数据库的频繁访问连接的次数,可以提高动态、数据库驱动之类网站的运行速度。 Memcached在使用是比较简单的,在操作上基本…...

嵌入式学习笔记——STM32单片机开发前的准备
STM32单片机开发前的准备1.集成开发环境的选取STM32 CubeIDEKEIL_MDK2.KEIL_MDK环境搭建安装包获取及安装芯片包下载及安装工程建立(STM32F407VET6为例)1.新建工程文件夹2.新建工程3.安装ST-LINK以及CH340的驱动4.设置KEIL,并烧录本文重点1.集成开发环境的选取 前面…...

客户案例|FPGA研发管理解决方案:UniPro瀑布+敏捷 打造高效能组织
2023开年以来,新享科技项目管理软件UniPro收获一波客户侧的点赞好评。在过去一年中,UniPro不断与客户保持高频沟通,满足客户需求为出发点,以产品功能实现为落脚点,不断打磨产品。 以UniPro客户京微齐力为例࿰…...
【信息学奥赛】1400:统计单词数
统计单词数也需要分割单词,如果使用字符数组来做的话,其实和1144:单词翻转类似,但是我一直只能通过四个样例,估计边界处理条件还是有点问题。 不过经过打印字符串长度之后发现了之前遇到的一个问题,即fget…...

# 技术详解: 利用CI同步文章以及多端发布
技术详解: 利用CI同步文章以及多端发布 技术详解: 利用CI同步文章以及多端发布 前言文章的同步实现的细节 思路文章元数据的定义和提取修改文章的优化本地图片资源上传CDN并替换本地link 终于到了 CI 的部分了最后来一些碎碎念 前言 前几天我更新了一篇简单技术总结之后&am…...
分形维数的计算方法汇总
以下是常用的时间序列分形维数计算方法及相应的参考文献:Hurst指数法Hurst指数法是最早用于计算分形维数的方法之一,其基本思想是通过计算时间序列的长程相关性来反映其分形特性。具体步骤是:(1) 对原始时间序列进行标准化处理。(2) 将序列分…...
微积分小课堂:积分(从微观趋势了解宏观变化)
文章目录 引言I. 预备知识: 积分效应1.1 闯黄灯1.2 公司利润(飞轮效应)1.3 飞轮效应II 积分2.1 积分的计算2.2 积分思想的本质引言 微分解决的问题是从宏观变化了解微观趋势;积分和微分刚好相反,是从微观去看宏观变化。 通过积分效应,提升我们的认识水平,同时能用一些工…...

4道数学题,求解极狐GitLab CI 流水线|第4题:合并列车
本文来自: 武让 极狐GitLab 高级解决方案架构师 💡 极狐GitLab CI 依靠其一体化、轻量化、声明式、开箱即用的特性,在开发者群体中的使用率越来越高,在国内企业中仅次于 Jenkins ,排在第二位。 极狐GitLab 流水线有 4…...
代码规范简述
目录 命名规范 代码格式 OOP规约 集合规范 并发规范 SQL语句规范 SQL 建表规范 SQL 索引规范 SQL 查询规范 控制语句规范 Javadoc 规范 其他规范 命名规范 1、包名:使用小写字母,多个单词之间用"."分隔,例如ÿ…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...

自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...