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

双向链表专题

在之前的单链表专题中,了解的单链表的结构是如何实现的,以及学习了如何实现单链表得各个功能。单链表虽然也能实现数据的增、删、查、改等功能,但是要找到尾节点或者是要找到指定位置之前的节点时,还是需要遍历链表这就会使得程序的效率受到影响。因此在本篇中就要来学习另外一种链表——双向链表,在此链表中在实现以上的功能时,不再需要遍历。接下来就开始双向链表专题的学习吧!!!


1.链表的分类 

在学习双向链表的实现前我们先要来了解链表可以分为哪些,链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构


 

列表的带头是指在链表中存在“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的” 。单向还是双向就表示链表是在一个链表的节点中是只能找到下一个节点还是能找到下一个节点并且还能找到前一个节点。循环还是不循环是指链表是否能通过尾节点找到头节点。

因此这些链表就可以按照带头还是不带头,单向还是双向,循环还是不循环分为以下的形式

在这些链表中虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表

单链表就是之前我们实现过的,其具体是不带头单向不循环的链表,而在本篇中要学习的双链表具体就是带头双向循环的链表。

2. 双向链表的实现 

2.1双链表的结构 

在实现双链表中也是和单链表一样用结构体来表示节点,但在双链表中中表示结构体的节点内部与单链表中有所不同,在其内部会多一个指向前节点的指针变量。

在此在实现节点时定义一个结构体struct ListNode来表示结构体的节点,在其内部有三个成员变量,第一个是一个整型变量data来表示节点中存放的数据信息,第二个是一个结构体指针来存放上一个节点的地址,第三个是一个结构体指针来存放下一个节点的地址。

typedef int LTDataType;//将int重命名,方便之后要修改数据类型时修改
typedef struct ListNode//将结构体重命名,简化结构体的名称
{LTDataType data;//节点内数据struct ListNode* prev;//上一个节点的指针struct ListNode* next;//下一个节点的指针
}LTNode;

2.2程序文件的设置

以下是双链表文件的设置以及各文件中要实现的内容

注:在SList.h中在文件的头部写入以下代码中要使用到的库函数的头文件

2.3初始化双链表

由于我们实现的双链表是带头的,也就是带有哨兵位节点的,所以在双链表中不同于单链表是需要先初始化的。在初始化中就是要创建一个哨兵位的节点。

首先要在List.h内内完成初始化双链表函数的声明

void LTInit(LTNode** pphead);//初始化双链表

将该函数命名为LTInit,该函数的的参数是链表中第一个结构体指针的地址

由于在双链表中也是要在插入等函数中都创建新的节点,所以将创建新节点封装到函数NewNode内,之后再要创建新节点就只需调用该函数

LTNode* NewNode(LTDataType x)//创建新节点
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("newnode!");exit(1);}newnode->data = x;newnode->prev = newnode->next = newnode;
}

在NewNode函数内在使用malloc申请内存空间后就将要存入的数据赋值给节点中的data,并且要在创建新的节点后就要使得该节点是循环的,这就要让newnode内的next指针和prev指针都指向该新节点


 

之后就是在List.c内完成函数的实现

因为在哨兵位节点内的数据应是随机的,所以在此初始化双链表中创建哨兵位节点时就只需要在调用NewNode函数时传的参数为之后都不会使用到的值,在此传的是-1 

void LTInit(LTNode** pphead)//初始化双链表(即创建哨兵位节点)
{*pphead = NewNode(-1);
}

 2.4展示双链表

首先要在List.h内完成对打印双链表函数的声明

void LTPrint(LTNode* phead);//打印双链表

将该函数命名为LTPrint,由于不用改变函数的参数,也就是不需要改变指向链表中第一个节点的指针的指向,使用该打印函数进行的是传值调用函数的参数就是指向链表第一个节点的指针

之后就是在List.c内完成对打印函数的实现

 在实现打印函数的代码前先来分析如何来遍历双链表,如以下双链表

一开始让pcur指向头节点的下一个节点,之后打印节点数据后就使得pcur指向该节点的下一个节点,也就是让pcur等于pcur->next,在此一直遍历下去直到pcur指向头节点时就停止pcur的移动。这样就可以完成对双链表的遍历。

因此通过以上的示例分析就可以得出循环的结束条件为pcur=phead,所以在while循环的判断部分就为pcur!=phead 

void LTPrint(LTNode* phead)//打印双链表
{LTNode* pcur = phead->next;while (pcur!=phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}

2.5在链表中查找指定数据

要实现一个函数能查找在双链表中是否存在要查找的数据,首先就要在List.h内声明该函数 

LTNode* LTFind(LTNode* phead, LTDataType x);//查找指定数据

将该函数命名为LTFind,由于查找是未对函数的参数作出改变,所以该函数进行的也是传值调用函数的参数有两个,一个是节点的结构体指针,另一个是要查找的数据,函数的返回类型是LTNode*的结构体指针

之后就是在List.c内完成查找函数的实现

在查找数据是否存在也是要遍历双链表,判断每个节点内的数据是否与要查找的数据相同,若相同就返回节点的指针。如果在遍历完双链表后都未找到与要查找数据相同的节点,这时就返回NULL

LTNode* LTFind(LTNode* phead, LTDataType x)//查找
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur ->next;}return NULL;
}


2.6双链表各功能的实现

2.6.1尾插

要实现一个函数能在双链表的尾部插入数据,首先要在List.h内完成对尾插函数的声明

void LTPushBack(LTNode* phead, LTDataType x);//尾插

将该函数命名为LTPushBack函数的参数有两个,一个是节点的结构体指针 ,另一个是要插入的数据

接下来我们就来分析如何在双链表中实现尾插,例如以下图示要在双链表的末尾插入节点,也就是要在在d3节点后插入新的节点要进行什么样的操作呢?

这时就只需要作出以下的改变就可以实现尾插
将新节点内的prev指针指向d3节点;next指针指向phead节点,再将d3的next指针指向新节点,再将phead的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中尾插新的节点 

完成的尾插的分析后之后就是在List.c内完成尾插函数的实现

在尾插过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向头节点phead;将nownode的next指针指向原尾节点。将头节点phead的prev指针指向newnode,将原链表的尾节点phead->prev的next指针指向newnode

在尾插过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空所以要给phead进行assert断言

void LTPushBack(LTNode* phead, LTDataType x)//尾插
{assert(phead);LTNode* newnode = NewNode(x);//phead   phead->prev newnode //改变新节点newnode的指向newnode->next = phead;newnode->prev = phead->prev;//改变尾节点的指向phead->prev->next = newnode;//改变头节点的指向phead->prev = newnode;
}

2.6.2头插 

要实现一个函数能在双链表的头部插入数据,首先要在List.h内完成对头插函数的声明

void LTPushFront(LTNode* phead, LTDataType x);//头插

将该函数命名为LTPushFront函数的参数有两个,一个是节点的结构体指针 ,另一个是要插入的数据

接下来我们就来分析如何在双链表中实现头插,例如以下图示要在双链表的头部插入节点,也就是要在在head节点后插入新的节点要进行什么样的操作呢?

这时就只需要作出以下的改变就可以实现头插
将新节点内的prev指针指向phead节点;next指针指向d1节点,再将phead的next指针指向新节点,再将d1的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中头插新的节点 

完成的头插的分析后之后就是在List.c内完成头插函数的实现

在头插过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向头节点phead;将nownode的next指针指向原头节点的下一个节点phead->next。将头节点phead的next指针指向newnode,将原链表的原头节点的下一个节点phead->next的prev指针指向newnode 

在头插过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空所以要给phead进行assert断言 

void LTPushFront(LTNode* phead, LTDataType x)//头插
{assert(phead);LTNode* newnode = NewNode(x);//phead newnode phead->next//改变新节点newnode的指向newnode->next = phead->next;newnode->prev = phead;//改变头节点下一个节点的指向phead->next->prev = newnode;//改变头节点的指向phead->next = newnode;
}

2.6.3尾删

要实现一个函数能删除双链表尾部的数据,首先要在List.h内完成对尾删函数的声明

void LTPopBack(LTNode* phead);//尾删

将该函数命名为LTPopBack函数的参数为节点的结构体指针 

接下来我们就来分析如何在双链表中实现尾部删除,例如以下图示要删除双链表尾部节点,也就是要释放d3节点的空间要进行什么样的操作呢?

这时就只需要作出以下的改变就可以实现尾删
先让头节点的prev指针指向原尾节点前的节点,再让原尾节点前的节点的next指针指向头节点,再释放原来的尾节点
完成这些操作就可以实现在原本的双链表中删除尾部的节点 

 

完成的尾删的分析后之后就是在List.c内完成尾删函数的实现

在尾删过程中本质就是按照以上图所示进行相关的更改,以上创建一个指向原尾节点的指针del,这样可以避免改变指针节点指向后找不到原来的尾节点。之后将头节点phead的prev指针指向del之前的节点del->prev,将原链表的del指向的节点的前一个节点del->next的next指针指向phead。最后再将del指针指向的节点释放,后将指针del置为NULL  

在尾删过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空所以要给phead进行assert断言 
同时尾删过程中只有哨兵节点时,无法完成删除,所以要给phead->next!=phead进行assert断言 

void LTPopBack(LTNode* phead)//尾删
{assert(phead && phead->next != phead);//phead phead->prev->prev  phead->prevLTNode* del = phead->prev;//phead del->prev delphead->prev = del->prev;del->prev->next = phead;free(del);//删除del节点del = NULL;
}

2.6.4头删 

要实现一个函数能删除双链表尾部的数据,也就是删除头节点之后的第一个节点,首先要在List.h内完成对头删函数的声明

void LTPopFront(LTNode* phead);//头删

将该函数命名为LTPopFront函数的参数为节点的结构体指针

接下来我们就来分析如何在双链表中实现头部删除,例如以下图示要删除双链表头部节点,也就是要释放d1节点的空间要进行什么样的操作呢? 

这时就只需要作出以下的改变就可以实现尾删
先让头节点的next指针指向原尾节点前的节点,再让原尾节点后两位的节点的prev指针指向头节点,再释放原来的head节点之后的节点
完成这些操作就可以实现在原本的双链表中删除尾部的节点 

 

 完成的头删的分析后之后就是在List.c内完成头删函数的实现

在头删过程中本质就是按照以上图所示进行相关的更改,以上创建一个指向原头节点之后的第一个节点的指针del,这样可以避免改变指针节点指向后找不到原来的尾节点。之后将头节点phead的next指针指向del之后的节点del->next,将原链表的del指向的节点的后一个节点del->next的prev指针指向phead。最后再将del指针指向的节点释放,后将指针del置为NULL  

在头删过程中要对头节点的结构体指针进行解引用,所以头节点指针不能为空所以要给phead进行assert断言 
同时头删过程中只有哨兵节点时,无法完成删除,所以要给phead->next!=phead进行assert断言 

void LTPopFront(LTNode* phead)//头删
{assert(phead &&phead->next!=phead);//phead phead->next phead->next->nextLTNode* del = phead->next;//phead del del->nextphead->next = del->next;del->next->prev = phead;free(del);//删除del节点del = NULL;
}

 

2.6.5 在指定位置之后插入

要实现一个函数能在双链表指定位置后插入数据,首先要在List.h内完成对在指定位置之后插入函数的声明

void LTInsert(LTNode* pos, LTDataType x);//在指定pos位置插入

将该函数命名为LTInsert函数的参数有两个,一个是要插入位置节点的结构体指针,另一个是要插入的数据

接下来我们就来分析如何在双链表中实现在指定位置之后插入节点,例如以下图示要在双链表d1节点后插入新的节点,要进行什么样的操作呢? 

这时就只需要作出以下的改变就可以实现在指定位置之后插入
将新节点内的prev指针指向d1节点;next指针指向d2节点,再将d1的next指针指向新节点,再将d2的prev指针指向新节点。完成这些操作就可以实现在原本的双链表中在指定位置之后插入新的节点 

 

 完成的在指定位置之后插入的分析后之后就是在List.c内完成在指定位置之后插入函数的实现

在在指定位置之后插入的过程中本质就是按照以上图所示进行相关的更改,将新节点newnode的prev指针指向pos节点;将nownode的next指针指向原pos节点的下一个节点pos->next。将pos节点的next指针指向newnode,将原链表的原pos节点的下一个节点pos->next的prev指针指向newnode

在指定位置之后插入过程中要对pos节点的结构体指针进行解引用,所以pos指向的节点指针不能为空所以要给pos进行assert断言 

void LTInsert(LTNode* pos, LTDataType x)//在指定pos位置之后插入
{assert(pos);//pos newnode pos->nextLTNode* newnode = NewNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

2.6.6删除指定位置

要实现一个函数能删除双链表指定位置的数据,首先要在List.h内完成对指定位置删除函数的声明

void LTErase(LTNode* pos);//删除指定pos位置的节点

将该函数命名为LTErase函数的参数为要删除位置节点的结构体指针 

接下来我们就来分析如何在双链表中实现指定位置删除,例如以下图示要删除d2节点,也就是要释放d2节点的空间要进行什么样的操作呢? 

这时就只需要作出以下的改变就可以实现删除指定位置的节点
先让d1节点的next指针指向d3节点,再让d3节点的prev指针指向d1节点,再释放原来的d2节点
完成这些操作就可以实现在原本的双链表中删除d2节点

 

完成的指定位置删除的分析后之后就是在List.c内完成指定位置删除函数的实现

在删除指定位置也就是删除pos指针指向的节点过程中本质就是按照以上图所示进行相关的更改,将pos指针指向的节点的前一个节点pos->prev的next指针指向pos指针指向的节点之后的节点pos->next,将pos指针指向的节点的后一个节点pos->next的prev指针指向pos指针指向的节点之前的节点pos->prev。最后再将pos指针指向的节点释放,后将指针pos置为NULL  

在删除指定位置过程中要对pos节点的结构体指针进行解引用,所以pos指向的节点指针不能为空所以要给pos进行assert断言 

void LTErase(LTNode* pos)//删除指定pos位置的节点
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}

 但这时就会存在一个问题销毁双链表函数进行的是传值调用,形参的改变无法影响实参,所以在以下代码中最后将pos=NULL,不会使得调用函数外的结构体指针置为NULL,这就需要在调用完该函数后再将结构体指针置为NULL

 

2.7销毁双链表 

由于我们实现的双链表的节点空间是动态内存开辟的,所以在双链表中是需要在使用完后销毁节点的。

首先要在List.h内内完成销毁双链表函数的声明

void LTDestory(LTNode* phead);//销毁双链表

将该函数命名为LTDestory,该函数的的参数是链表中第一个结构体指针的地址

之后就是在List.c内实现销毁函数
以下函数是通过遍历的方法来将链表中的节点一个个释放,最后遍历完出函数时就只剩下phead指向的头节点,这时再释放该节点就可以将双链表全部销毁

void LTDestory(LTNode* phead)//销毁双链表
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}

但这时就会存在一个问题销毁双链表函数进行的是传值调用,形参的改变无法影响实参,所以在以下代码中最后将phead=NULL,不会使得调用函数外的结构体指针置为NULL,这就需要在调用完该函数后再将结构体指针置为NULL

#include"List.h"void test()
{LTNode* plist = NULL;plist=LTInit();//测试尾插LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPrint(plist);//测试销毁双链表LTDestory(plist);plist = NULL;
}int main()
{test();return 0;
}

那么有的读者就会发出疑问:为什么不直接在LTErase函数和LTDestory的参数直接传传二级指针呢?
其实理论上LTErase和LTDestory参数理论上是要传二级,因为我们需要让形参的改变影响到实参,但是为了保持接口的一致性才传的一级指针
传一级指针的问题是:当形参phead置为NULL后,实参plist不会被修改为NULL,因此解决方法是调用完函数后手动将实参置为NULL


 

3.双链表完整代码

为了保持接口的一致性可以将初始化函数修改为以下形式

LTNode* LTInit()//初始化双链表
{LTNode* phead = NewNode(-1);return phead;
}

 

List.h

#define  _CRT_SECURE_NO_WARNINGS 1
#pragma once 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}LTNode;//void LTInit(LTNode** pphead);//初始化双链表
LTNode* LTInit();//初始化双链表void LTDestory(LTNode* phead);//销毁双链表void LTPrint(LTNode* phead);//打印双链表
LTNode* LTFind(LTNode* phead, LTDataType x);//查找void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPushBack(LTNode* phead, LTDataType x);//尾插void LTPopFront(LTNode* phead);//头删
void LTPopBack(LTNode* phead);//尾删void LTInsert(LTNode* pos, LTDataType x);//在指定pos位置插入
void LTErase(LTNode* pos);//删除指定pos位置的节点

 

List.c

#include"List.h"LTNode* NewNode(LTDataType x)//创建新节点
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("newnode!");exit(1);}newnode->data = x;newnode->prev = newnode->next = newnode;
}void LTPrint(LTNode* phead)//打印双链表
{LTNode* pcur = phead->next;while (pcur!=phead){printf("%d ->", pcur->data);pcur = pcur->next;}printf("\n");
}LTNode* LTFind(LTNode* phead, LTDataType x)//查找
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur ->next;}return NULL;
}//void LTInit(LTNode** pphead)//初始化双链表(即创建哨兵位节点)
//{
//	*pphead = NewNode(-1);
//}LTNode* LTInit()//初始化双链表
{LTNode* phead = NewNode(-1);return phead;
}void LTDestory(LTNode* phead)//销毁双链表
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;}void LTPushFront(LTNode* phead, LTDataType x)//头插
{assert(phead);LTNode* newnode = NewNode(x);//phead newnode phead->nextnewnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}void LTPushBack(LTNode* phead, LTDataType x)//尾插
{assert(phead);LTNode* newnode = NewNode(x);//phead   phead->prev newnode newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}void LTPopFront(LTNode* phead)//头删
{assert(phead &&phead->next!=phead);//phead phead->next phead->next->nextLTNode* del = phead->next;//phead del del->nextphead->next = del->next;del->next->prev = phead;free(del);//删除del节点del = NULL;}void LTPopBack(LTNode* phead)//尾删
{assert(phead && phead->next != phead);//phead phead->prev->prev  phead->prevLTNode* del = phead->prev;//phead del->prev delphead->prev = del->prev;del->prev->next = phead;free(del);//删除del节点del = NULL;
}void LTInsert(LTNode* pos, LTDataType x)//在指定pos位置插入
{assert(pos);//pos newnode pos->nextLTNode* newnode = NewNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;}void LTErase(LTNode* pos)//删除指定pos位置的节点
{assert(pos);//pos->prev pos pos->nextpos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}


 

相关文章:

双向链表专题

在之前的单链表专题中&#xff0c;了解的单链表的结构是如何实现的&#xff0c;以及学习了如何实现单链表得各个功能。单链表虽然也能实现数据的增、删、查、改等功能&#xff0c;但是要找到尾节点或者是要找到指定位置之前的节点时&#xff0c;还是需要遍历链表&#xff0c;这…...

SpringCoud组件

一、使用SpringCloudAlibaba <dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2023.0.1.0</version><…...

向量的定义和解释

这是一个向量&#xff1a; 向量具有大小&#xff08;大小&#xff09;和方向&#xff1a; 线的长度显示其大小&#xff0c;箭头指向方向。 在这里玩一个&#xff1a; 我们可以通过将它们从头到尾连接来添加两个向量&#xff1a; 无论我们添加它们的顺序如何&#xff0c;我们都…...

IoTDB 集群高效管理:一键启停功能介绍

如何快速启动、停止 IoTDB 集群节点的功能详解&#xff01; 在部署 IoTDB 集群时&#xff0c;对于基础的单机模式&#xff0c;启动过程相对简单&#xff0c;仅需执行 start-standalone 脚本来启动 1 个 ConfigNode 节点和 1 个 DataNode 节点。然而&#xff0c;对于更高级的分布…...

一个spring boot项目的启动过程分析

1、web.xml 定义入口类 <context-param><param-name>contextConfigLocation</param-name><param-value>com.baosight.ApplicationBoot</param-value> </context-param> 2、主入口类: ApplicationBoot,SpringBoot项目的mian函数 SpringBo…...

智驭未来:人工智能与目标检测的深度交融

在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;如同一股不可阻挡的浪潮&#xff0c;正以前所未有的速度重塑着我们的世界。在众多AI应用领域中&#xff0c;目标检测以其独特的魅力和广泛的应用前景&#xff0c;成为了连接现实与智能世界的桥梁。本文旨在…...

01MFC建立单个文件类型——画线

文章目录 选择模式初始化文件作用解析各初始化文件解析 类导向创建鼠标按键按下抬起操作函数添加一个变量记录起始位置注意事项代码实现效果图 虚实/颜色线 选择模式 初始化文件作用解析 运行&#xff1a; 各初始化文件解析 MFC&#xff08;Microsoft Foundation Classes&am…...

免杀中用到的工具

&#x1f7e2; 绝大部分无法直接生成免杀木马&#xff0c;开发、测试免杀时会用到。 工具简称 概述 工具来源 下载路径 x64dbg 中文版安装程序(Jan 6 2024).exe 52pojie hellshell 官方的加密或混淆shellcode github Releases ORCA / HellShell GitLab hellshe…...

[vite] Pre-transform error: Cannot find package pnpm路径过长导致运行报错

下了套vue3的代码&#xff0c;执行pnpm install初始化&#xff0c;使用vite启动&#xff0c;启动后访问就会报错 报错信息 ERROR 16:40:53 [vite] Pre-transform error: Cannot find package E:\work\VSCodeProjectWork\jeecg\xxxxxxxxx-next\xxxxxxxxx-next-jeecgBoot-vue3\…...

Promise总结

Promise.then() 的返回值仍然是 Promise 对象 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>D…...

ROI 接口便捷修改

传入的图片截取ROI后再进入识别接口 &#xff08;识别接口比ROI接口的函数参数少一个传入的ROI&#xff09; 无点只有点集 返回双点集 //平直冷侧翅片 bool ImageProcessingTest::straightColdSideFin_ROI(cv::Mat img, cv::Rect ROI, std::vector<cv::Point>& topL…...

jenkins打包java项目报错Error: Unable to access jarfile tlm-admin.jar

jenkins打包boot项目 自动重启脚本失败 查看了一下项目日志报错&#xff1a; Error: Unable to access jarfile tlm-admin.jar我检查了一下这个配置&#xff0c;感觉没有问题&#xff0c;包可以正常打&#xff0c; cd 到项目目录下面&#xff0c;手动执行这个sh脚本也是能正常…...

SQL Server设置端口:跨平台指南

在使用SQL Server时&#xff0c;设置或修改其监听的端口是确保数据库服务安全访问和高效管理的重要步骤。由于SQL Server可以部署在多种操作系统上&#xff0c;包括Windows、Linux和Docker容器等&#xff0c;因此设置端口的步骤和方法也会因平台而异。本文将为您提供一个跨平台…...

ActiveMQ-CVE-2023-46604

Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞 OpenWire协议在ActiveMQ中被用于多语言客户端与服务端通信。在Apache ActvieMQ5.18.2版本以及以前&#xff0c;OpenWire协议通信过程中存在一处反序列化漏洞&#xff0c;该漏洞可以允许具有网络访问权限的远程攻击者通过操作…...

TensorBoard ,PIL 和 OpenCV 在深度学习中的应用

重要工具介绍 TensorBoard&#xff1a; 是一个TensorFlow提供的强大工具&#xff0c;用于可视化和理解深度学习模型的训练过程和结果。下面我将介绍TensorBoard的相关知识和使用方法。 TensorBoard 简介 TensorBoard是TensorFlow提供的一个可视化工具&#xff0c;用于&#x…...

【超音速 专利 CN117576413A】基于全连接网络分类模型的AI涂布抓边处理方法及系统

申请号CN202311568976.4公开号&#xff08;公开&#xff09;CN117576413A申请日2023.11.22申请人&#xff08;公开&#xff09;超音速人工智能科技股份有限公司发明人&#xff08;公开&#xff09;张俊峰&#xff08;总&#xff09;; 杨培文&#xff08;总&#xff09;; 沈俊羽…...

iPhone数据恢复篇:iPhone 数据恢复软件有哪些

问题&#xff1a;iPhone 15 最好的免费恢复软件是什么&#xff1f;我一直在寻找一个恢复程序来恢复从iPhone中意外删除的照片&#xff0c;联系人和消息&#xff0c;但是我有很多选择。 谷歌一下&#xff0c;你会发现许多付费或免费的iPhone数据恢复工具&#xff0c;声称它们可…...

Html5+Css3学习笔记

Html5 CSS3 一、概念 1.什么是html5 html: Hyper Text Markup Language ( 超文本标记语言) 文本&#xff1a;记事本 超文本&#xff1a; 文字、图片、音频、视频、动画等等&#xff08;网页&#xff09; html语言经过浏览器的编译显示成超文本 开发者使用5种浏览器&#xf…...

WPF学习(2) -- 样式基础

一、代码 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/2008&…...

独家揭秘!五大内网穿透神器,访问你的私有服务

本文精心筛选了五款炙手可热的内网穿透工具&#xff0c;它们各怀绝技&#xff0c;无论您是企业用户、独立开发者&#xff0c;还是技术探索者&#xff0c;这篇文章都物有所值&#xff0c;废话不多说&#xff0c;主角们即将上场。 目录 1. 巴比达 - 安全至上的企业护航者 2. 花…...

Ubuntu 编译和运行ZLMediaKit

摘要 本文描述了如何在Ubuntu上构建ZLMediaKIt项目源码&#xff0c;以及如何体验其WebRTC推流和播放功能。 实验环境 操作系统版本&#xff1a;Ubuntu 22.04.3 LTS gcc版本&#xff1a;11.4.0 g版本&#xff1a;11.4.0 依赖库安装 #让ZLMediaKit媒体服务器具备WebRTC流转发…...

基于JavaSpringBoot+Vue+uniapp微信小程序校园宿舍管理系统设计与实现

基于JavaSpringBootVueuniapp微信小程序实现校园宿舍管理系统设计与实现 目录 第一章 绪论 1.1 研究背景 1.2 研究现状 1.3 研究内容 第二章 相关技术介绍 2.1 Java语言 2.2 HTML网页技术 2.3 MySQL数据库 2.4 Springboot 框架介绍 2.5 VueJS介绍 2.6 ElementUI介绍…...

Hive的基本操作(创建与修改)

必备知识 数据类型 基本类型 类型写法字符char, varchar, string✔整数tinyint, smallint, int✔, bigint✔小数float, double, numeric(m,n), decimal(m,n)✔布尔值boolean✔时间date✔, timestamp✔ 复杂类型(集合类型) 1、数组&#xff1a;array<T> 面向用户提供…...

Linux开发讲课37--- ARM的22个常用概念

1. ARM中一些常见英文缩写解释 MSB&#xff1a;最高有效位&#xff1b; LSB&#xff1a;最低有效位&#xff1b; AHB&#xff1a;先进的高性能总线&#xff1b; VPB&#xff1a;连接片内外设功能的VLSI外设总线&#xff1b; EMC&#xff1a;外部存储器…...

7-1、2、3 IPFS介绍使用及浏览器交互(react+区块链实战)

7-1、2、3 IPFS介绍使用及浏览器交互&#xff08;react区块链实战&#xff09; 7-1 ipfs介绍7-2 IPFS-desktop使用7-3 reactipfs-api浏览器和ipfs交互 7-1 ipfs介绍 IPFS区块链上的文件系统 https://ipfs.io/ 这个网站本身是需要科学上网的 Ipfs是点对点的分布式系统 无限…...

CentOS 7 中出现 cannot open Packages database in /var/lib/rpm 错误

转载自:https://www.jianshu.com/p/423306f43e72 # 进入 rpmdb 所在目录 [roothostbase ~]# cd /var/lib/rpm [roothostbase rpm]# ls Basenames __db.001 __db.003 Group Name Packages Requirename Sigmd5 Conflictname __db.002 Dirnames Ins…...

【java深入学习第6章】深入解析Spring事件监听机制

在Spring框架中&#xff0c;事件监听机制是一个强大且灵活的功能&#xff0c;允许我们在应用程序中发布和监听事件。这种机制可以帮助我们实现松耦合的设计&#xff0c;使得不同模块之间的通信更加灵活和可维护。本文将详细介绍Spring的事件监听机制&#xff0c;并通过代码示例…...

Flask与Celery实现Python调度服务

文章目录 Flask与Celery实现Python调度服务一、前言1.组件2.场景说明3.环境 二、安装依赖1.安装Anaconda3.安装redis2.安装依赖包 三、具体实现1.目录结构2.业务流程3.配置文件4.Celery程序5.Flask程序6.测试脚本7.程序启动1&#xff09;Windows开发调试2&#xff09;Linux服务…...

Eureka应用场景和优势

Eureka是一款由Netflix开源的服务注册与发现框架&#xff0c;在微服务架构中扮演着至关重要的角色。以下是Eureka的应用场景和优势&#xff1a; Eureka的应用场景 Eureka主要应用于微服务架构中&#xff0c;特别是在大型、复杂的分布式系统中&#xff0c;用于管理和发现服务。…...

prompt第三讲-PromptTemplate

文章目录 前提回顾PromptTemplateprompt 模板定义以f-string渲染格式以mustache渲染格式以jinja2渲染格式直接实例化PromptTemplatePromptTemplate核心变量 prompt value生成invokeformat_prompt(不建议使用)format(不建议使用) batchstreamainvoke PromptTemplate核心方法part…...