当前位置: 首页 > news >正文

【数据结构】吃透单链表!!!(详细解析~)

目录

  • 前言:
  • 一.顺序表的缺陷 && 介绍链表
    • 1.顺序表的缺陷
    • 2.介绍链表
      • (1)链表的概念
      • (2)链表的结构
      • (3)链表的功能
  • 二.单链表的实现
    • 1.创建节点的结构
    • 2.头文件函数的声明
    • 3.函数的实现
      • (1)打印单链表
      • (2)创建一个节点
      • (3)尾插
      • (4)头插
      • (5)尾删
      • (6)头删
      • (7)查找
      • (8)在pos位置前插入
      • (9)在pos位置后插入
      • (10)删除pos位置
      • (11)删除pos位置后的节点
      • (12)清理单链表
  • 三.全部代码
    • 1.SList.h
    • 2.SList.c
    • 3.Test.c

前言:

上篇文章介绍了顺序表,这篇文章开始着重讲解链表了。
链表有很多种:单、双链表,循环、非循环链表还有带头、不带头的链表。本篇的主要内容是单链表(无头,单向,非循环)
链表对比顺序表有哪些不同之处,接下来会带大家一起了解~

一.顺序表的缺陷 && 介绍链表

1.顺序表的缺陷

1.头部和中间的插入删除效率都较低,时间复杂度为O(N)。需要挪动数据。
2.空间不够用了,增容需要申请新空间拷贝数据释放旧空间。会有不小的消耗。(尤其是异地扩容)
3.扩容会有一定的空间浪费。(例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间)

2.介绍链表

针对顺序表的缺陷,就有了链表这个数据结构

(1)链表的概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
特点:按需申请释放
在这里插入图片描述

顺序表是数组存储数据的,空间是连续的(可通过一个指针找到所有的值),通过size标记直到没有数据(前面的为size的个数即有效数据)。
链表的每个节点的大小没有关系,也不连续(多次malloc开辟出来的空间是随机的)。它是通过一个头指针(phead)先找到第一个节点,然后通过第一个节点的指针找到第二个节点,第二个节点的指针找到第三个节点,以此类推(通过指针链接)。每个位置的节点都有指针指向下一个,当下一个为空指针的时候,就结束。

(2)链表的结构

物理图:
在这里插入图片描述
逻辑图:
在这里插入图片描述
链表的节点组成(单链表):
在这里插入图片描述

注意:链表的最后一个节点的next指向空

看到这有些小伙伴可能有些疑惑,链表的每个节点是不连续的,为什么上面的两个图中每个节点都有线连接起来变成看似连续的呢?其实不是这样的,以上的两张图是为了方便理解。实际在内存中每个节点的地址是随机的,只不过用这个节点的指针(next)找到了下一个节点的地址,所以才能实现链接。

(3)链表的功能

链表的功能与顺序表类似,无非是增删查改,在某位置的插入与删除,对数据内容进行管理和操作。

二.单链表的实现

还是以多文件的形式分模块写

SList.h——函数和类型的声明
SList.c——函数的实现
Test.c——进行测试

1.创建节点的结构

单链表一个节点的结构:

存放数据:data
结构体指针:next

注意:不能这样写:

typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{SListDataType data;SLTNode* next;
}SLTNode;//  <-重定义开始生效的位置

因为typedef重定义结构体类型的名称是在上面有箭头的一行开始生效,生效了才能使用,在前面就提前使用就会出现错误。

正确写法:

typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SLTNode;

2.头文件函数的声明

1.打印单链表
2.创建一个节点
3.尾插
4.头插
5.尾删
6.头删
7.查找(包含修改)
8.在pos位置前插入
9.在pos位置后插入
10.删除pos位置的节点
11.删除pos位置后一个的节点
12.清理单链表

//打印单链表
void SLTPrint(SLTNode* phead);
//创建一个节点
SLTNode* BuySLTNode(SListDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x);
//删除pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后一个的节点
void SLTEraseAfter(SLTNode* pos);
//清理单链表
void SLTDestroy(SLTNode** pphead);

3.函数的实现

(1)打印单链表

创建一个结构体指针变量(cur),使它指向第一个节点(把头指针覆给cur)。利用循环如果cur不是空指针,就打印cur所指向的数据,然后cur往后走(到下一个节点)。直到cur为空跳出,最后打印NULL(最后一个节点为空指针)。

逻辑图:
在这里插入图片描述
物理图:
在这里插入图片描述
注意:与顺序表不同,顺序表传过来的指针一定不为空;链表传过来的指针可能为空,比如链表没有节点,头指针指向的就是NULL,所以不需要断言头指针。

所以在测试的文件里(Test.c)刚开始要让头指针指向NULL

SLTNode * plist = NULL;

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

(2)创建一个节点

为了方便后面的尾插、头插等操作,所以写个函数来创建一个新节点。
新节点的类型也是结构体指针,用malloc函数开辟一个新节点。如果新节点为空就报错。然后给新节点的data赋值,next为空,返回这个节点(方便其他的函数使用)

SLTNode* BuySLTNode(SListDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}

(3)尾插

尾插一个新节点,假设有多个节点,首先要找到尾,定义一个变量tail去遍历链表找到尾
注意:用tail遍历找尾再尾插时不能写成:

       SLTNode* tail = *pphead;while (tail){tail = tail->next;}tail = newnode;		

这段代码看似没有什么问题,其实是与正确的代码差别很大。
tail刚开始指向第一个节点,如果不为空,到下一个节点;当tail为空时跳出循环,把newnode的值(新节点的地址)赋给tail。
如图:
在这里插入图片描述
这里有一个问题,tail里面存放的是新节点的地址,但是原来链表的最后一个节点的next指针并没有存放新节点的地址,也就是说最后一个节点没有与新节点连接起来,就没有尾插了。其次还有可能存在内存泄漏,新创建的节点丢了。

因为tail是局部变量,newnode和phead也是,它们出这个函数就销毁了,所以给tail这个变量赋新节点的地址没有用。

要成功完成尾插,就必须改变结构体的内容,让最后一个节点的next指针指向新节点的地址。

这里大家可能有些疑惑,既然tail销毁了,那么链表的这些节点会不会销毁呢?
答案是不会,因为这些节点是malloc出来的,malloc在堆上开辟的空间,只有自己主动free释放掉才能销毁。

正确的思路:
首先想到的是要改变结构体(节点)的内容,那么tail这个指针变量就不能到空结束,而是到最后一个节点结束(tail的next为空就结束,tail的位置指向最后一个节点)。

此时尾节点的next为空,我们要做的是让尾节点的next存放新节点的地址。让tail的next存放newnode的值(新节点的地址),就可以改变结构体的内容。
在这里插入图片描述

找尾尾插正确的一小段代码:

		SLTNode* tail = *pphead;while (tail->next){tail = tail->next;}tail->next = newnode;

还有一种情况,如果刚开始链表没有节点,就不需要找尾了。直接将新节点的地址给头指针(plist)就行

但是这种情况要注意什么呢?
以下是错误示范:

	if (phead == NULL){phead = newnode;}

这个代码的意思如图所示:
在这里插入图片描述
有两个问题:
一:plist没有改变,还是指向空指针;新节点丢了,可能造成内存泄漏。
二:newnode和phead是形参,形参是实参的拷贝,出这个函数就销毁了,改变phead并没有改变plist。

注意!!!:plist是一级指针,改变一级指针需要用到二级指针,并且有解引用操作。所以在函数的参数应该用二级指针来接收(传参时plist要有取地址符才能与二级指针类型对应)
在这里插入图片描述
正确的一小段代码:

	if (*pphead == NULL){*pphead = newnode;}

总结:
1.改变结构体,要用结构体指针
2.改变结构体指针,要有结构体指针的指针(即二级指针)

最后一点:什么时候要断言指针
当一级指针(* pphead)为空时不需要断言,因为如果刚开始链表没有节点,* pphead所指向的就是空指针。二级指针pphead存放的是一级指针的地址,一级指针的地址不可能为空,所以二级指针需要断言。

void SLTPushBack(SLTNode** pphead, SListDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);//原来没有节点,改变结构体指针,用二级指针if (*pphead == NULL){*pphead = newnode;}//原来有节点,改变结构体,用结构体指针else{SLTNode* tail = *pphead;while (tail->next){tail = tail->next;}tail->next = newnode;}
}

(4)头插

头插也需要用到二级指针,因为每次头插头指针(plist)都要连接新的节点。(改变了头指针)
头插时原来链表没有节点与原来链表有节点的思路是一样的
在这里插入图片描述
新节点连接第一个节点或者空指针,然后plist连接新节点

注意:两者的顺序不能换,因为如果先让plist连接newnode,那么原来链表plist头指针后面的节点就找不到了。newnode再连接plist所指向的下一个节点就是自己,导致死循环。

void SLTPushFront(SLTNode** pphead, SListDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}

(5)尾删

前面的尾插、头插都有用到二级指针,那么尾删需不需要二级指针呢?接下来我们一点一点的分析:

尾删的大体思路是:找到尾,然后free释放掉尾节点就行。

但是链表有一个很重要的点:前后关联

这里我们定义一个指针变量tail去找尾,把尾节点删掉了,那么原来前一个节点变成新的尾节点,还需要用另一个变量当作原来尾节点的前一个节点,新的尾节点next指针就必须指向NULL只需要改变结构体),否则就访问野指针了。
有两种写法,这里只展现一种,就用tail一个指针变量,让它的下一个的下一个指针为空时停下(tail->next->next==NULL),此时tail->next就是最后一个节点,tail是前一个节点,修改新的尾节点的next,让tail->next为NULL(改变结构体)就行了。

以上只是包括一类情况:一个以上节点的时候是这样的
如果尾删把节点只删到剩下一个节点时,还是如此吗?

在这里插入图片描述
按前面的思路来走,遇到尾节点就把它的前一个节点的next置空。

依图分析,只有一个节点时,前一个节点就不是节点了,是头指针。要让头指针指向NULL,即改变头指针,就要用到二级指针了。
让 * pphead置空,就可以改变头指针

plist 等价于 * pphead

没有节点的情况:
断言 * pphead,为空就不能再删了

void SLTPopBack(SLTNode** pphead)
{assert(pphead);//空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//一个以上的节点else{SLTNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

(6)头删

通过前面的分析发现,有改变头指针所指向的内容就要用到二级指针,头删是把第一个节点除去,让头指针指向新的头节点。

画图分析:
在这里插入图片描述
当链表没有节点时不能再删了,所以要对 * pphead断言( * pphead等价于plist即第一个节点)

只有一个节点和有多个节点不需要分开处理,定义一个变量记录原来链表的第二个节点(新的头节点),free释放掉第一个节点,让头指针连接新的头节点

void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空assert(*pphead);//非空SLTNode* newhead = (*pphead)->next;//注意优先级free(*pphead);//不需要置空,因为头指针直接连接新的头*pphead = newhead;
}

(7)查找

定义一个变量cur遍历链表,先判断cur所指向的数据是否等于x,如果相等,返回cur,否则往后走;找不到返回空指针。

SLTNode* SLTFind(SLTNode* phead, SListDataType x)
{assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

查找可以包含修改这个节点的数据

	SLTNode* pos1 = SLTFind(plist, 2);//测试查找+修改if (pos1 != NULL){printf("找到了\n");pos1->data *= 100;SLTPrint(plist);}else{printf("找不到\n");}

(8)在pos位置前插入

要在pos位置前插入一个新节点,首先pos这个位置的节点必须存在,所以要断言pos(后面有pos位置插入删除的函数也要用到)
pos可能在任意一个位置,如果pos在第一个节点,就相当于头插了。头插要改变头指针所指向的内容,所以要用二级指针。直接调用头插的函数即可。

pos不在第一个节点的情况:
首先要定义一个变量prev,遍历链表找到并指向pos的前一个节点,因为插入新的节点必须前后连接起来(单链表的不足之处,后期文章用双向循环带头链表就非常简单)。
当prev->next != pos,往后走;==pos时跳出循环,让prev->next连接新节点,新节点的next连接pos,完成插入。
在这里插入图片描述

void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x)
{assert(pphead);assert(pos);//pos在第一个节点就是头插if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}

(9)在pos位置后插入

因为在pos位置后插入新的节点,所以可以不用头指针了,找到pos位置的下一个节点即可。可以定义一个变量posNext为pos位置的下一个节点,让新节点的next连接posNext,pos->next连接新节点
不需要考虑是不是尾插,因为在哪插入都是一样的
在这里插入图片描述

void SLTInsertAfter(SLTNode* pos, SListDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);SLTNode* posNext = pos->next;newnode->next = posNext;pos->next = newnode;
}

(10)删除pos位置

删除pos位置的节点,必须把它的前一个节点与后一个节点连接起来,这里就要有头指针,去找pos位置的前一个节点。
我们要考虑一些情况,pos在第一个节点、中间某个节点和尾节点

当pos在第一个节点时,就是头删,要改变头指针指向的内容,所以要用二级指针,然后调用头删的函数即可

如果pos是在中间的某个节点或者尾节点呢?
其实两者的思路是一致的,把pos位置的节点删除,让前一个节点连接后一个节点就行(是尾节点的话,让前一个节点连接空指针)
在这里插入图片描述

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

(11)删除pos位置后的节点

要删除pos位置的后一个节点,除了pos这个位置要存在之外,pos位置的后一个节点也必须存在,所以pos->next要断言。假如pos是在尾节点,就没有意义了。

定义一个变量posNext为pos的下一个节点,然后使pos->next指向posNext->next,即把pos位置的节点与posNext的下一个节点连接起来,最后释放掉posNext

在这里插入图片描述

void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//检查是否为尾节点SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);posNext = NULL;
}

(12)清理单链表

清理(销毁)链表,必须要一个一个节点清理,因为链表在物理结构上是不连续的。

定义一个变量cur遍历链表,每到一个节点把它释放掉。但是这里又有一个问题,当前节点被释放了,怎么到下一个节点呢?所以我们循环里再定义一个变量next为cur的下一个节点,释放完当前的cur,然后把next赋给cur,这样cur就能到下一个节点了。

最后全部节点释放完,头指针要指向空,这里又有改变头指针了,所以有二级指针。
在这里插入图片描述

void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

三.全部代码

1.SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SListDataType;//方便更改存储的数据类型
typedef struct SListNode
{SListDataType data;struct SListNode* next;
}SLTNode;
//打印单链表
void SLTPrint(SLTNode* phead);
//创建一个节点
SLTNode* BuySLTNode(SListDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x);
//删除pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后一个的节点
void SLTEraseAfter(SLTNode* pos);
//清理单链表
void SLTDestroy(SLTNode** pphead);

2.SList.c

#include "SList.h"
//打印
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
//创建一个节点
SLTNode* BuySLTNode(SListDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SListDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);//原来没有节点,改变结构体指针,用二级指针if (*pphead == NULL){*pphead = newnode;}//原来有节点,改变结构体,用结构体指针else{SLTNode* tail = *pphead;while (tail->next){tail = tail->next;}tail->next = newnode;}
}
//头插
void SLTPushFront(SLTNode** pphead, SListDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead);//空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//一个以上的节点else{SLTNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}
//头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead);//空assert(*pphead);//非空SLTNode* newhead = (*pphead)->next;//注意优先级free(*pphead);//不需要置空,因为头指针直接连接新的头*pphead = newhead;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SListDataType x)
{assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SListDataType x)
{assert(pphead);assert(pos);//pos在第一个节点就是头插if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SListDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);SLTNode* posNext = pos->next;newnode->next = posNext;pos->next = newnode;
}
//删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
//删除pos位置后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//检查是否为尾节点SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);posNext = NULL;
}
//清理
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* next = cur->next;free(cur);cur = next;}*pphead = NULL;
}

3.Test.c

#include "SList.h"
test()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);//测试尾插SLTPrint(plist);SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);//测试头插SLTPrint(plist);SLTPopBack(&plist);SLTPopBack(&plist);SLTPopBack(&plist);//测试尾删SLTPrint(plist);SLTPopFront(&plist);SLTPopFront(&plist);//测试头删SLTPrint(plist);SLTNode* pos1 = SLTFind(plist, 2);//测试查找+修改if (pos1 != NULL){printf("找到了\n");pos1->data *= 100;SLTPrint(plist);}else{printf("找不到\n");}SLTNode* pos2 = SLTFind(plist, 10);//测试pos位置前插入if (pos2){SLTInsert(&plist, pos2, 66);SLTPrint(plist);}SLTNode* pos3 = SLTFind(plist, 20);//测试pos位置后插入if (pos3){SLTInsertAfter(pos3, 77);SLTPrint(plist);}SLTNode* pos4 = SLTFind(plist, 1);//测试删除pos位置if (pos4){SLTErase(&plist, pos4);SLTPrint(plist);}SLTNode* pos5 = SLTFind(plist, 66);//测试删除pos位置的后一个节点if (pos5){SLTEraseAfter(pos5);SLTPrint(plist);}SLTDestroy(&plist);
}
int main()
{test();return 0;
}

在这里插入图片描述
总算把最费劲的写完了,感谢铁子们的观看,期待大家的支持~

相关文章:

【数据结构】吃透单链表!!!(详细解析~)

目录 前言&#xff1a;一.顺序表的缺陷 && 介绍链表1.顺序表的缺陷2.介绍链表&#xff08;1&#xff09;链表的概念&#xff08;2&#xff09;链表的结构&#xff08;3&#xff09;链表的功能 二.单链表的实现1.创建节点的结构2.头文件函数的声明3.函数的实现&#xff…...

Linux 安全技术和防火墙

目录 1 安全技术 2 防火墙 2.1 防火墙的分类 2.1.1 包过滤防火墙 2.1.2 应用层防火墙 3 Linux 防火墙的基本认识 3.1 iptables & netfilter 3.2 四表五链 4 iptables 4.2 数据包的常见控制类型 4.3 实际操作 4.3.1 加新的防火墙规则 4.3.2 查看规则表 4.3.…...

Mac 开发 Tang Nano FPGA 指南(使用终端和使用 VS Code 和插件,适用所有 Gowin FPGA)

最近收到了一个 Tang nano 9K FPGA开发板&#xff0c;就想借此机会研究一下。 官方文档里介绍如果想使用高云的 FPGA&#xff0c;就需要使用 GOWIN IDE&#xff0c;但是需要申请 license 提交一堆资料&#xff0c;我是别人送的就不太方便让别人弄。加上 IDE 其实并不是很适合学…...

基于深度学习的铁路异物侵限检测算法研究_整体认知感觉欠点意思,但是有一个新的变形卷积-Octave 卷积

相比于其他的交通运输方式&#xff0c;铁路运输具有准时性高、连续性强、速度快、运输量大、运输成本低以及安全可靠等优点。同时由于国家高速铁路网络建设的不断推进&#xff0c;铁路运输逐渐成为我国客运与货运的主要运输方式。虽然铁路运输为人们出行和货物运输带来的极大的…...

Spring项目使用Redis限制用户登录失败的次数以及暂时锁定用户登录权限

文章目录 背景环境代码实现0. 项目结构图&#xff08;供参考&#xff09;1. 数据库中的表&#xff08;供参考&#xff09;2. 依赖&#xff08;pom.xml&#xff09;3. 配置文件&#xff08;application.yml&#xff09;4. 配置文件&#xff08;application-dev.yml&#xff09;5…...

2023.8 - java - 变量类型

在Java语言中&#xff0c;所有的变量在使用前必须声明。声明变量的基本格式如下&#xff1a; type identifier [ value][, identifier [ value] ...] ; 格式说明&#xff1a; type -- 数据类型。identifier -- 是变量名&#xff0c;可以使用逗号 , 隔开来声明多个同类型变量…...

【Kubernetes】Kubernetes的Pod控制器

Pod控制器 一、Pod 控制器的概念1. Pod 控制器及其功用2. Pod 控制器有多种类型2.1 ReplicaSet2.2 Deployment2.3 DaemonSet2.4 StatefulSet2.5 Job2.6 Cronjob 3. Pod 与控制器之间的关系 二、Pod 控制器的使用1. Deployment2. SatefulSet2.1 为什么要有headless&#xff1f;2…...

Ubuntu20.04安装Nvidia显卡驱动教程

1、禁用nouveau 1、创建文件&#xff0c;如果没有下载vim编辑器&#xff0c;将vim换成gedit即可 $ sudo vim /etc/modprobe.d/blacklist-nouveau.conf 2、在文件中插入以下内容&#xff0c;将nouveau加入黑名单&#xff0c;默认不开启 blacklist nouveau options nouveau m…...

视频汇聚/视频云存储/视频监控管理平台EasyCVR添加萤石云设备详细操作来啦!

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…...

AI 绘画Stable Diffusion 研究(十二)SD数字人制作工具SadTlaker插件安装教程

免责声明: 本案例所用安装包免费提供&#xff0c;无任何盈利目的。 大家好&#xff0c;我是风雨无阻。 想必大家经常看到&#xff0c;无论是在产品营销还是品牌推广时&#xff0c;很多人经常以数字人的方式来为自己创造财富。而市面上的数字人收费都比较昂贵&#xff0c;少则几…...

数据结构——链表详解

链表 文章目录 链表前言认识链表单链表结构图带头单循环链表结构图双向循环链表结构图带头双向循环链表结构图 链表特点 链表实现(带头双向循环链表实现)链表结构体(1) 新建头节点(2) 建立新节点(3)尾部插入节点(4)删除节点(5)头部插入节点(6) 头删节点(7) 寻找节点(8) pos位置…...

(学习笔记-进程管理)什么是悲观锁、乐观锁?

互斥锁与自旋锁 最底层的两种就是 [互斥锁和自旋锁]&#xff0c;有很多高级的锁都是基于它们实现的。可以认为它们是各种锁的地基&#xff0c;所以我们必须清楚它们之间的区别和应用。 加锁的目的就是保证共享资源在任意时间内&#xff0c;只有一个线程访问&#xff0c;这样就…...

actuator/prometheus使用pushgateway上传jvm监控数据

场景 准备 prometheus已经部署pushgateway服务&#xff0c;访问{pushgateway.server:9091}可以看到面板 实现 基于springboot引入支持组件&#xff0c;版本可以 <!--监控检查--><dependency><groupId>org.springframework.boot</groupId><artifa…...

Linux设置临时目录路径的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

19-普通组件的注册使用

普通组件的注册使用-局部注册 一. 组件注册的两种方式:1.局部注册:只能在注册的组件内使用 (1) 创建 vue 文件(单文件组件) (2) 在使用的组件内导入,并注册 components:{ 组件名: 组件对象 } // 导入需要注册的组件 import 组件对象 from.vue文件路径 import HmHeader from ./…...

Java基础篇:抽象类与接口

1、抽象类和接口的定义&#xff1a; &#xff08;1&#xff09;抽象类主要用来抽取子类的通用特性&#xff0c;作为子类的模板&#xff0c;它不能被实例化&#xff0c;只能被用作为子类的超类。 &#xff08;2&#xff09;接口是抽象方法的集合&#xff0c;声明了一系列的方法…...

面对对象编程范式

本文是阅读《设计模式之美》的总结和心得&#xff0c;跳过了书中对面试和工作用处不大或不多的知识点&#xff0c;总结总共分为三章&#xff0c;分别是面对对象编程范式、设计原则和设计模式 现如今&#xff0c;编程范式存在三种&#xff0c;它们分别是面向对象编程、面向过程编…...

“深度学习”学习日记:Tensorflow实现VGG每一个卷积层的可视化

2023.8.19 深度学习的卷积对于初学者是非常抽象&#xff0c;当时在入门学习的时候直接劝退一大班人&#xff0c;还好我坚持了下来。可视化时用到的图片&#xff08;我们学校的一角&#xff01;&#xff01;&#xff01;&#xff09;以下展示了一个卷积和一次Relu的变化 作者使…...

146. LRU 缓存

题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否…...

Unity框架学习--场景切换管理器

活动场景 用脚本实例化的游戏对象都会生成在活动场景中。 哪个场景是活动场景&#xff0c;则当前的天空盒就会使用该场景的天空盒。 只能有一个场景是活动场景。 在Hierarchy右击一个场景&#xff0c;点击“Set Active Scene”可以手动把这个场景设置为活动场景。也可以使用…...

Kotlin Lambda和高阶函数

Lambda和高阶函数 本文链接&#xff1a; 文章目录 Lambda和高阶函数 lambda输出&#xff08;返回类型&#xff09;深入探究泛型 inline原理探究 高阶函数集合、泛型自己实现Kotlin内置函数 扩展函数原理companion object 原理 > 静态内部类函数式编程 lambda 1、lambda的由…...

ELKstack-Elasticsearch配置与使用

一. 部署前准备 最小化安装 Centos 7.x/Ubuntu x86_64 操作系统的虚拟机&#xff0c;vcpu 2&#xff0c;内存 4G 或更多&#xff0c; 操作系统盘 50G&#xff0c;主机名设置规则为 es-server-nodeX &#xff0c; 额外添加一块单独的数据磁盘 大小为 50G 并格式化挂载到/data/e…...

Kotlin 基础教程二

constructor 构造器一般情况下可以简化为主构造器 即: class A constructor(参数) : 父类 (参数) 也可以在构造器上直接声明属性constructor ( var name) 这样可以全局访问 init { } 将和成员变量一起初始化 susped 挂起 data class 可以简化一些bean类 比如get / set ,自动…...

K8S deployment挂载

挂载到emptyDir 挂载在如下目录&#xff0c;此目录是pod所在的node节点主机的目录&#xff0c;此目录下的data即对应容器里的/usr/share/nginx/html&#xff0c;实现目录挂载&#xff1b;图1红框里的号对应docker 的name中的编号&#xff0c;如下俩个图 apiVersion: apps/v1 k…...

类之间的比较

作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 类之间的比较 固定需求式比较器 固定需求式 通过…...

设计模式之备忘录模式(Memento)的C++实现

1、备忘录模式的提出 在软件功能开发过程中&#xff0c;某些对象的状态在转换过程中&#xff0c;由于业务场景需要&#xff0c;要求对象能够回溯到对象之前某个点的状态。如果使用一些共有接口来让其他对象得到对象的状态&#xff0c;便会暴露对象的实现细节。备忘录模式是在不…...

学习笔记230804---restful风格的接口,delete的传参方式问题

如果后端提供的删除接口是restful风格&#xff0c;那么使用地址栏拼接的方式发送请求&#xff0c;数据放在主体中&#xff0c;后端接受不到&#xff0c;当然也还有一种可能&#xff0c;后端在这个接口的接参设置上是req.query接参。 问题描述 今天遇到的问题是&#xff0c;de…...

STM32使用IIC通信的引脚配置问题

STM32使用IIC通信的引脚配置问题 在使用IIC通信时&#xff0c;遇到引脚配置问题&#xff0c;记录一下&#xff1a; IIC的两个引脚SDA和SCL都要求既能输入又能输出。 问题&#xff1a; SDA线是由不同的器件分时控制的&#xff0c;这样就会有一个问题&#xff1a;当一个器件主动…...

题解 | #K.First Last# 2023牛客暑期多校10

K.First Last 签到题 题目大意 n n n 个人参加 m m m 场比赛&#xff0c;每场比赛中获得名次得概率均等 问针对某一人&#xff0c;他在所有场次比赛中都获得第一或倒数第一的概率 解题思路 如果人数 n > 1 n>1 n>1 &#xff0c;每场比赛的概率是 p 2 n p\dfra…...

Python 程序设计入门(025)—— 使用 os 模块操作文件与目录

Python 程序设计入门&#xff08;025&#xff09;—— 使用 os 模块操作文件与目录 目录 Python 程序设计入门&#xff08;025&#xff09;—— 使用 os 模块操作文件与目录一、操作目录的常用函数1、os 模块提供的操作目录的函数2、os.path 模块提供的操作目录的函数 二、相对…...

excel逻辑函数篇1

1、AND(logical1,[logical2],…)&#xff1a;用于测试所有条件是否均为TRUE 检查所有参数均为true&#xff0c;如果是则返回true 2、OR(logical1,[logical2],…)&#xff1a;用于测试是否有为TRUE的条件 如果任意参数值为true&#xff0c;即返回true&#xff1b;只有当所有参数…...

前端基础(Vue的模块化开发)

目录 前言 响应式基础 ref reactive 学习成果展示 Vue项目搭建 总结 前言 前面学习了前端HMTL、CSS样式、JavaScript以及Vue框架的简单适用&#xff0c;接下来运用前面的基础继续学习Vue&#xff0c;运用前端模块化编程的思想。 响应式基础 ref reactive 关于ref和react…...

SystemVerilog interface使用说明

1. Interface概念 System Verilog中引入了接口定义&#xff0c;接口与module 等价的定义&#xff0c;是要在其他的接口、module中直接定义&#xff0c;不能写在块语句中&#xff0c;跟class是不同的。接口是将一组线捆绑起来&#xff0c;可以将接口传递给module。 2. 接口的优…...

机器人制作开源方案 | 送餐机器人

作者&#xff1a;赖志彩、曹柳洲、王恩开、李雪儿、杨玉凯 单位&#xff1a;华北科技学院 指导老师&#xff1a;张伟杰、罗建国 一、作品简介 1. 场景调研 1.1项目目的 近年来&#xff0c;全国多地疫情频发&#xff0c;且其传染性极高&#xff0c;食品接触是传播途径之一。…...

Gradio部署应用到服务器不能正常访问

用Gradio部署一个基于ChatGLM-6B的应用&#xff0c;发布到团队的服务器上&#xff08;局域网&#xff0c;公网不能访问&#xff09;&#xff0c;我将gradio应用发布到服务器的9001端口 import gradio as gr with gr.Blocks() as demo:......demo.queue().launch(server_port90…...

数据暴涨时代,该如何数据治理?_光点科技

随着信息技术的迅猛发展&#xff0c;数据已经成为现代社会的核心资源。在这个被称为"数据暴涨时代"的时代里&#xff0c;大量的数据源源不断地被产生和积累&#xff0c;但如何有效地管理、分析和利用这些数据成为了一个迫切需要解决的问题。数据治理&#xff0c;作为…...

2021年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;找和为K的两个元素 在一个长度为n(n < 1000)的整数序列中&#xff0c;判断是否存在某两个元素之和为k。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入 第一行输入序列的长度n和k&#xff0c;用空格分开。 第二行输入序列中的n个整数&#xff…...

GPT-5出世?OpenAI GPT-5商标已注册

OpenAI的GPT已经成为了业界标杆&#xff0c;升级速度之快让人瞠目&#xff0c;别人追GPT-3.5的时候GPT-4横空出世&#xff0c;差距被拉开了&#xff0c;现在GPT-5就要来了。 据商标律师泄露的消息&#xff0c;OpenAI已于7月18日注册了GPT-5商标。虽然注册商标并不罕见&#xf…...

优雅的使用 Dockerfile 定制镜像

一、使用 Dockerfile 定制镜像 1.1、Dockerfile 定制镜像 镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本&#xff0c;用这个脚本来构建、定制镜像&#xff0c;无法重复的问题、镜像构建透明性的问题、…...

limit的用法mysql

当你在 MySQL 数据库中使用 LIMIT 语句&#xff0c;它可以用于限制查询结果的行数。这在分页查询或者限制返回的数据量时非常有用。下面是一些示例&#xff1a; 返回前几行数据&#xff1a; SELECT * FROM Customers LIMIT 5;这将返回 Customers 表中的前 5 行数据。 分页查…...

clickhouse Checksum doesn‘t match: 问题修复

1. 问题描述 SQL 错误 [40] [07000]: Code: 40. DB::Exception: Checksum doesnt match: corrupted data. Reference: 50e8c1efa78de2881b725d44b04be1fe. Actual: 161c99eb681ec36b83540ecdd65ad8c9. Size of compressed block: 32846. The mismatch is caused by single bit…...

企业如何开展个人信息安全影响评估(PIA)二

基本概念 根据《信息安全 技术个人信息安全影响评估指南》&#xff08;GB/T 39335—2020&#xff1b;personal information security impact assessment&#xff0c;简称“PIA”&#xff09;&#xff0c;个人信息安全影响评估是指针对个人信息处理活动&#xff0c;检验其合法合…...

android TextView 超出长度使用省略号

在Android中最常见的需求&#xff0c;就是在在外部展示信息时&#xff0c;需要简要展示内容。TextView仅需在静态布局文件中设置以下几个属性&#xff1a; android:maxWidth“100dp” // 宽度是多少才算超出 android:maxLines"2" // 高度多少才算超出 android:elli…...

c++ | 字节转换 | 字长 | 机器位数

为什么有的时候脑子转不过来&#xff1f;&#xff1f; 为什么要对字节、机器长啊、位啊都要门清 位数 一般的就是指计算机的位数&#xff0c;比如64位/32位&#xff0c;更简单的理解&#xff0c;计算机就是在不停的做二进制的计算&#xff0c;比如32位计算机&#xff0c;在长…...

7-4 交换最小值和最大值

分数 15 全屏浏览题目 切换布局 作者 C课程组 单位 浙江大学 本题要求编写程序&#xff0c;先将输入的一系列整数中的最小值与第一个数交换&#xff0c;然后将最大值与最后一个数交换&#xff0c;最后输出交换后的序列。 注意&#xff1a;题目保证最大和最小值都是唯一的。…...

解决Pycharm的Settings中Project不见了也无法选择Python Interpreter的方法

目录 一、问题如下二、解决方法 一、问题如下 突然打开项目没有python解释器&#xff0c;也无法重新配置python Interpreter&#xff0c;而且整个文件夹是黄色高亮的形式&#xff0c;如下显示&#xff0c;而且重新安装了pycharm也没用甚至说打开File–>Setting–>Projec…...

AWS EKS 集群自动扩容 Cluster Autoscaler

文章目录 一&#xff0c;需求工作需求说明 二&#xff0c;部署精简命令执行1&#xff0c;要求2&#xff0c;查看EC2 Auto Scaling groups Tag3&#xff0c;创建Serviceaccount需要的Policy&#xff0c;Role4&#xff0c;部署Cluster Autoscaler5&#xff0c;验证6&#xff0c;常…...

excel中有哪些通配符、excel配置问题,数学函数篇1之sum系列

学习excel前需要明确的是事&#xff1a;   在学习excel函数之前&#xff0c;大家需要明确一件事&#xff0c;excel现在设计到了一些新函数&#xff0c;这些新函数只能存在于office365、office2019及更 新版本之中&#xff0c;所以建议大家在学习时安装较新的版本&#xff0c;…...

工控行业需要熟悉的工业级通信协议

1. Modbus RTU Modbus RTU 是一种串行通信协议&#xff0c;常用于工控行业。它具有以下特点和优势&#xff1a; 采用二进制编码&#xff0c;数据传输更加高效。支持多种传输介质&#xff0c;如RS-232、RS-485等。容易实现和使用&#xff0c;适用于各种设备。具备强大的错误检…...

力扣题解(1030. 距离顺序排列矩阵单元格),带注释

题目描述 链接:点我 注意&#xff1a;多看几遍题目&#xff0c;开始没看懂…相当于计算矩阵网格里面的点&#xff08;不要计算边界&#xff09; 我开了题解才明白题的意思 orz… 题解 class Solution {public int[][] allCellsDistOrder(int rows, int cols, int rCenter, …...