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

【C++】封装map和set(红黑树实现)

前言:

       前面,我们学习了set和map的用法,这两个容器可以完成查找,排序等操作,后来我们在学习过二叉搜索树的基础上又学习了两种特殊的二叉搜索树——AVL树和红黑树,他们俩可以是效率进一步提高,其实set和map的底层就是由红黑树封装而成的!

       所以,本篇文章我们自己学习用红黑树封装set和map。用红黑树封装,特别是用同一棵红黑树实现封装有一定的难度,其中很多操作(比如复用)我们会第一次尝试,所以大家请跟紧作者的脚步来。

目录

(一)如何复用同一棵红黑树

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

2、模拟实现结点的比较大小

3、改造后的红黑树

4、map和set迭代器的模拟实现

4.1模版参数的构成

4.2begin()和end()的模拟实现

4.3operator* 和 operator->模拟实现

4.4 operator++ 和 operator--模拟实现

4.5 operator== 和 operator!=模拟实现

4.6迭代器代码详解

 (三)封装map和set


(一)如何复用同一棵红黑树

前提疑问:

在我们这所有的之前我们知道,map和set这两个容器都是用红黑树来实现的,那么就有了接下来的问题。

  • map和set都是用的同一棵红黑树复用的吗
  • 或者这两个容器各自使用一棵红黑树吗

其实在前言中我们就知道了,根据STL的设计理念和泛型编程的理念,我们不会冗余的写两课红黑树,而是用一棵红黑树实现复用的效果。

我们调用STL库中的原码来看:

我们发现:

  • STL库中的原码也确实调用的同一棵红黑树
  • 他们实现的都是key_value模型

我们设set中存放结点的值是K,map中存放的节点是pair<key,value>键值对:

  • 对于set而言,底层红黑树的模版就是RBTree<K, K>
  • 对于map而言,底层红黑树的模版就是RBTree<K, pair<K, V>>

这时我们就有了另一个疑问,两个模板参数的第一个Key,不能省略掉吗??

  • 首先,答案肯定是不能的
  • 那么原因又是什么呢?

因为map的这个类中,无论怎么省略都会有一个查找函数要单独用到Key

如果第一个Key删去了,那么map和set的查找函数就没法统一实现了,违背了我们一开始泛型编程的思想。

综上所述:

  • map和set都是用了,Key_value模型
  • set中的K是K,V也是K
  • map中的K是K,V是pair<K,V>
  • 并且模板参数中第一个K都不能省

(二)红黑树的改造流程(为封装做铺垫)

1、结点的定义

这里由于set和map存放的结点一个是Key一个是pair<Key,Value>,所以我们使用模版,把存放的结点泛化。

2、模拟实现结点的比较大小

  • 上述提到,在模拟实现中,map和set我们复用同一棵红黑树的时候都是用的是Kye_value的结构
  • 但是红黑树中的数据比较又是Key值的比较,而现在我们用的则是pair的比较
  • 虽然编译上是可以通过但是真的就是我们所想要的吗?

pair的比较大小:

很显然这种比较规则不是我们所想要的,并且map和set想要取到用来比较的数据是不同的。 

为了取到我们想要的数据,我们引入了仿函数:

  • 根据map和set的需求不同
  • 我们在红黑树中新引入了一个模板参数KeyOfT

 

引入KeyOfT模板参数后,我们想要不同方式的比较方法只需要在set和map的封装中给出属于他们自己的比较仿函数。

map中的仿函数: 

set中的仿函数:

使用方法:

 此时前面所说的仿函数的便利之处就体现出来了。

3、改造后的红黑树

具体代码:

//红黑树的实现
//KeyOfT --> 支持取出T对象中key的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> iterator;typedef __RBTreeIterator<T, const T&, const T*> const_iterator;//构造 拷贝构造 赋值 和析构 跟搜索树实现方式是一样的//迭代器中序遍历,要找最左结点iterator Begin(){Node* subLeft = _root;while (subLeft && subLeft->_left){subLeft = subLeft->_left;}//树的迭代器用结点的指针就可以构造return iterator(subLeft);}iterator End(){return iterator(nullptr);}const_iterator Begin() const{Node* subLeft = _root;while (subLeft && subLeft->_left){subLeft = subLeft->_left;}//树的迭代器用结点的指针就可以构造return const_iterator(subLeft);}const_iterator End() const{return const_iterator(nullptr);}pair<iterator, bool> Insert(const T& data){//1、搜索树的规则插入//2、看是否违反平衡规则,如果违反就需要处理:旋转if (_root == nullptr){_root = new Node(data);_root->_col = BLACK; //根节点是黑色return make_pair(iterator(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}//找到符合规则的位置之后再插入cur = new Node(data); Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}//三叉链的链接 -- 链上父节点cur->_parent = parent;//存在连续红色结点while (parent && parent->_col == RED){//理论而言,祖父是一定存在的,父亲存在且是红不可能是根(根一定是黑的)Node* grandfather = parent->_parent;assert(grandfather);if (grandfather->_left == parent){Node* uncle = grandfather->_right;//情况一:(叔叔存在且为红)if (uncle && uncle->_col == RED){//祖父和叔叔变成黑色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}//情况二:(叔叔不存在 or 叔叔存在且为黑)else{//单旋//	   g//	 p// cif (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//双旋//    g//  p//    celse{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//无论父亲和叔叔是左是右都是一样的//grandfather->_right == parent;else{Node* uncle = grandfather->_left;//情况一:if (uncle && uncle->_col == RED){//祖父和叔叔变成黑色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else{//单旋// g//   p//     cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//双旋//  g//    p//  celse{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//父亲为空就出循环,将根节点设置成黑色_root->_col = BLACK;return make_pair(iterator(newnode), true);}
}

插入(Insert)和寻找(Find)代码:
 

Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<itertaor, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(itertaor(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(itertaor(cur), false);}}cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//     g//   p   u// c if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){//    g//  u   p//        cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{//    g//  u   p//        cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{//    g//  u   p//    cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(itertaor(newnode), true);;}

4、map和set迭代器的模拟实现

上面改造后的红黑树中已经涉及到了迭代器,这里我们来模拟实现。

备注:

  • T:数据
  • Ref:引用
  • Ptr:指针
  • Sef:iterator本身

4.1模版参数的构成

要想实现同一棵红黑树的复用,模版参数的构成极为重要。

之前我们也遇到过相似的情况,我们这里的实现方法参看之前。

迭代器的实现中:

  • 涉及到了*解引用操作,返回的是数据的引用;
  • 涉及到了->指针操作,返回的是数据的地址;
  • 涉及到++,--操作,返回的是操作后迭代器本身的位置。

但是以上的行为,我们都涉及结点,所以结点的数据类型也尤为重要

综上,模版参数的构成如下图:

4.2begin()和end()的模拟实现

  • 因为是二叉搜索树,为了更有顺序,所以我们采取的是中序遍历。
  • 那么中序遍历Begin()就应该是最左结点
  • 我们实现的版本中End()定义为空是(nullptr)

ps:

而库中的红黑树则是设计了一个哨兵位的头结点:

所以我们实现的只是简化版本。

4.3operator* 和 operator->模拟实现

就是正常的运用operator,但是operator->和list中讲的一样,返回的是地址的原因是为了连续访问(连续的operator->会优化成一个->)

4.4 operator++ 和 operator--模拟实现

这里迭代器的++和- - 需要分类一下,分别是:前置++,- - 、后置++,- -

前置++ :
因为我们是中序遍历,我们访问完自己之后,下一个该访问哪一个结点?

我们观察任何一棵二叉树都能看出,无非是下面两种情况:

分如下两种情况:(重点)

  • 右子树为空:找孩子是父亲的左的那个祖先节点,否则继续往上走,直到空(nullptr)
  • 右子树为非空:找右子树的最左节点

这一过程有点类似于递归思想,但是是非递归实现的

代码实现:

 前置-- :

有了上面的思路,我们联想一下,现在需要看当前位置左子树是否是空。

前置- -就是倒着走,同样还是对当前位置分两种情况:(重点)

  1. 左子树为空:找孩子是父亲的右的那个祖先节点,否则继续往上走,直到空(nullptr)
  2. 左子树为非空:找左子树的最右节点

只要前置++理解了,那么前置- -完全就是前置++倒过来走一遍。

后置++、后置- - :

和之前实现容器的得带器一样,我们这里直接复用即可:

4.5 operator== 和 operator!=模拟实现

有了之前封装迭代器的经验,这两个的视线还是比较容易得:

4.6迭代器代码详解

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;Node* _node;typedef __RBTreeIterator<T, Ref, Ptr> Self;__RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_right == nullptr){//找祖先里面,孩子是父亲左的那个Node* cur = _node;Node* parent = cur->_parent;while (parent && parent->_right == cur){cur = cur->_parent;parent = parent->_parent;}_node = parent;}else{//右子树的最左结点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}//左为空_node = subLeft;}return *this;}Self& operator--(){if (_node->_left == nullptr){//找祖先里面,孩子是父亲Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}else{//左子树的最右结点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}return *this;}Self operator++(int){Self tmp(*this);++(*this);return tmp;}Self operator--(int){Self tmp(*this);--(*this);return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

 (三)封装map和set

有了上面的红黑树的改装,我们这里的对map和set的封装就显得很得心应手了。

封装主要是把map和set的基本操作封装在一个类中。

map的封装:

template<class K, class V>
class map
{//定义一个内部类struct MapKeyOfT{const K& operator()(const pair<K, V>& kv)//operator() 可以像函数一样去使用{return kv.first;}};public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.Begin();}iterator end(){return _t.End();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}
private:RBTree<K, pair<K, V>, MapKeyOfT> _t;
};

这里map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

对set的封装:

template<class K>
class set    
{struct SetKeyOfT{const K& operator()(const K& key)//operator() 可以像函数一样去使用{return key;}};
public://加上typename告诉编译器这是个类型,类模板实例化了再去取typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin() const{return _t.Begin();}iterator end() const{return _t.End();}pair<iterator, bool> insert(const K& key){//pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);auto ret = _t.Insert(key);return pair<iterator, bool>(iterator(ret.first._node), ret.second);}iterator find(const K& key){return _t.Find(key);}private:RBTree<K, K, SetKeyOfT> _t;
};

附详细代码:——》红黑树封装map和set

相关文章:

【C++】封装map和set(红黑树实现)

前言&#xff1a; 前面&#xff0c;我们学习了set和map的用法&#xff0c;这两个容器可以完成查找&#xff0c;排序等操作&#xff0c;后来我们在学习过二叉搜索树的基础上又学习了两种特殊的二叉搜索树——AVL树和红黑树&#xff0c;他们俩可以是效率进一步提高&#xff0c;其…...

【补】代码随想录算法训练营day38|动态规划 |509. 斐波那契数|70. 爬楼梯|746. 使用最小花费爬楼梯

动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪心&#xff0c;贪心没有状态推…...

C语言sizeof()计算空间大小为8的问题

在练习数据结构过程中&#xff0c;定义指针p&#xff0c;并且申请了10个char类型空间&#xff0c;但在计算p所指空间大小时候&#xff0c;发现了一些奇怪的现象。 #include <stdio.h> #include <stdlib.h>int main(){char s[12];printf("the size of memory …...

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 LMD局部均值分解 直接替换Excel即可运行包含频谱图相关系数图 Matlab语言 1.算法新颖…...

景区AR虚拟三维场景沉浸式体验成为新兴的营销手段

科技的迅速崛起正在改变我们的世界&#xff0c;旅游业也在这股浪潮中掀起了一场全新的变革。增强现实(AR)技术正成为旅行中的一股强大力量&#xff0c;通过增添趣味和交互性&#xff0c;为旅程注入了前所未有的活力。本文将带您深入了解AR如何为旅游带来全新的体验&#xff0c;…...

【深度学习】 Python 和 NumPy 系列教程(五):Python容器:3、集合Set详解(初始化、访问元素、常用操作、常用函数)

目录 一、前言 二、实验环境 三、Python容器&#xff08;Containers&#xff09; 0、容器介绍 1、列表&#xff08;List&#xff09; 2、元组&#xff08;Tuple&#xff09; 3、集合&#xff08;Set&#xff09; 1. 初始化 2. 访问集合元素 3. 常用操作 a. 添加单个…...

单片机C语言实例:6、定时器的应用

一、定时器0控制LED闪烁 实例程序1&#xff1a; #include<reg52.h> //包含头文件&#xff0c;一般情况不需要改动&#xff0c;头文件包含特殊功能寄存器的定义sbit LED P1^2; //定义LED端口/*------------------------------------------------定时器初始化子程序 …...

ChatGPT Prompting开发实战(五)

一、如何编写有效的prompt 对于大语言模型来说&#xff0c;编写出有效的prompt能够帮助模型更好地理解用户的意图(intents)&#xff0c;生成针对用户提问来说是有效的答案&#xff0c;避免用户与模型之间来来回回对话多次但是用户不能从LLM那里得到有意义的反馈。本文通过具体…...

MySQL——DQL union合并、limit限制与DDL建表和删表

一、Union 合并 union:是实现两个查询结果的合并。 例如&#xff1a;当我们查询员工名字为manager 和 salesman的员工名字和 工作&#xff1f; select e.ename,e.job from emp e where e.jobmanager or e.job salesman; select e.ename,e.job from emp e where e.job in(man…...

Java“牵手”唯品会商品列表数据,关键词搜索唯品会商品数据接口,唯品会API申请指南

唯品会商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取唯品会商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问唯品会商城的网页来获取商品详情信息。以下是两种常用方法的介…...

Springboot整合JWT完成验证登录

目录 一、引入依赖二、JwtUtil 代码解读三、LoginController 代码解读四、整体代码五、结果展示 一、引入依赖 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></depende…...

centos7 下使用docker安装常见的软件:Redis

关于docker的基础知识&#xff0c;请见《别在说自己不知道docker了&#xff0c;全文通俗易懂的给你说明白docker的基础与底层原理》 在自己学习的过程中经常会需要动手安装一下常见的工具&#xff0c;本篇就手把手带你用docker安装一遍。 jdk安装 如果先要更换之前的jdk从第…...

sql:SQL优化知识点记录(十五)

&#xff08;1&#xff09;MySQL主从复制 我们这里配置一Windows上的MySql做主机&#xff0c;Linux上的MySql做从机&#xff0c;搭建一主一从 测试以下是否能够拼通&#xff1a;从Linux上&#xff1a;167&#xff0c;连接Windows的165 从Windows的165 连接Linux上&#xff1a;…...

vue3+ts 分享海报

安装依赖1. npm install html2canvas --save<div class"flex-box"><div><div v-for"(item,index ) in from.list" :key"index" click"actvieFuntion(index)"><div>{{item}}</div><div :class"…...

Ubuntu23.10将推出全磁盘加密功能,提高系统安全性

Canonical 宣布其即将推出的 Ubuntu 23.10&#xff08;Mantic Minotaur&#xff09;将引入基于 TPM 的全磁盘加密的初步支持。这个特性将利用系统可信平台模块&#xff08;TPM&#xff09;&#xff0c;在系统级别上进行全磁盘加密&#xff0c;从而提高系统的安全性。 但需要注…...

防火墙的设置主要是为了防范什么

防火墙的设置主要是为了防范网络攻击和数据泄露。随着互联网的普及和信息化的加速&#xff0c;网络安全问题越来越受到人们的关注。其中&#xff0c;防火墙是一种常见的网络安全设备&#xff0c;其设置的重要性也日益凸显。 防火墙的设置主要是为了防范什么 防火墙的设置主要目…...

Vim9和其他软件的文本复制、粘贴

大家都知道&#xff1a;在Vim9中使用y和p命令来进行文本的复制和粘贴&#xff0c;今天我来说一说Vim和其他软件之间的文本复制、粘贴操作。 Vim9和其他软件进行复制、粘贴&#xff0c;其原理就是通过系统剪贴板作为中介来执行操作。 一、从Vim9复制文本内容 按住鼠标左键滑出…...

MySQL学习5:事务、存储引擎

事务 简介 事务是一组数据库操作的执行单元&#xff0c;它要么完全执行&#xff0c;要么完全不执行。事务是确保数据库中的数据一致性和完整性的重要机制之一。 事务具有以下四个特性&#xff08;称为ACID特性&#xff09;&#xff1a; 原子性&#xff08;Atomicity&#xf…...

redis如何保证接口的幂等性

背景 如何防止接口中同样的数据提交&#xff0c;以及如何保证消息不被重复消费&#xff0c;这些都是shigen在学习的过程中遇到的问题。今天&#xff0c;趁着在学习redis的间隙&#xff0c;我写了一篇文章进行简单的实现。 注意&#xff1a;仅使用于单机的场景&#xff0c;对于…...

避坑之路 —— 前后端 json 的注意问题

当我们在进行开发项目的时候&#xff0c;在前后端需要进行数据之间的传输&#xff0c;那么就会需要到json。而json算是字符串中的一种 1.先说一下前端的, 其实这两种都是表示前端希望能收到后端json这样的数据格式&#xff0c;那么我们在后端就需要注意将数据进行转换为json进…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...