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

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

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

向量的定义和解释
这是一个向量: 向量具有大小(大小)和方向: 线的长度显示其大小,箭头指向方向。 在这里玩一个: 我们可以通过将它们从头到尾连接来添加两个向量: 无论我们添加它们的顺序如何,我们都…...

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

一个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…...

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

01MFC建立单个文件类型——画线
文章目录 选择模式初始化文件作用解析各初始化文件解析 类导向创建鼠标按键按下抬起操作函数添加一个变量记录起始位置注意事项代码实现效果图 虚实/颜色线 选择模式 初始化文件作用解析 运行: 各初始化文件解析 MFC(Microsoft Foundation Classes&am…...
免杀中用到的工具
🟢 绝大部分无法直接生成免杀木马,开发、测试免杀时会用到。 工具简称 概述 工具来源 下载路径 x64dbg 中文版安装程序(Jan 6 2024).exe 52pojie hellshell 官方的加密或混淆shellcode github Releases ORCA / HellShell GitLab hellshe…...
[vite] Pre-transform error: Cannot find package pnpm路径过长导致运行报错
下了套vue3的代码,执行pnpm install初始化,使用vite启动,启动后访问就会报错 报错信息 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后再进入识别接口 (识别接口比ROI接口的函数参数少一个传入的ROI) 无点只有点集 返回双点集 //平直冷侧翅片 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项目 自动重启脚本失败 查看了一下项目日志报错: Error: Unable to access jarfile tlm-admin.jar我检查了一下这个配置,感觉没有问题,包可以正常打, cd 到项目目录下面,手动执行这个sh脚本也是能正常…...
SQL Server设置端口:跨平台指南
在使用SQL Server时,设置或修改其监听的端口是确保数据库服务安全访问和高效管理的重要步骤。由于SQL Server可以部署在多种操作系统上,包括Windows、Linux和Docker容器等,因此设置端口的步骤和方法也会因平台而异。本文将为您提供一个跨平台…...

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

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

【超音速 专利 CN117576413A】基于全连接网络分类模型的AI涂布抓边处理方法及系统
申请号CN202311568976.4公开号(公开)CN117576413A申请日2023.11.22申请人(公开)超音速人工智能科技股份有限公司发明人(公开)张俊峰(总); 杨培文(总); 沈俊羽…...

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

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

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&…...

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

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...