从二叉树到 STL:揭开 set 容器的本质与用法

前言:

        上次介绍完二叉搜索树后,更新中断了一段时间,先向大家致歉。最近学习状态有些起伏,但我正在努力调整,相信很快会恢复节奏。今天我们继续深入探讨——关联容器,它在算法和工程中都非常常见和重要。

1.之前的遗漏

        我之前写的二叉搜索树其实没有写完,我仅仅写了一个节点只能存储一个值的二叉搜索树。在我们日常的工作中,这种树的使用率其实还是比较低的。最受欢迎的是里面存储两个值的二叉搜索树,这个可以类比Python中的字典。相信学过python的读者对此不陌生,字典其实存放了一对值,分别是Key和Value,类比英文字典中的英语和汉字,我们通过英文(Key)来找到对应的中文(Value)。这其实才是我们常使用到的二叉搜索树,下面我通过举例来帮助各位理解这两棵树的区别。

1.1.Key搜索场景

        举个例子,现在很多小区配有地下停车库。业主的车牌号会录入系统中,车辆进入时由系统识别并判断是否允许进入。这就是典型的 Key 搜索场景 —— 只需根据一个关键字(车牌号)进行查找。

1.2.Key/Value搜索场景

        还是以我们的生活举例,如今我们进入各大商场的时候,如果开车的话难免会需要停进商场专用的停车场,此时这就是停车场计费系统,我们需要录入两个值:车子的车牌号以及入场时间,记录好之后,当车辆想要离开停车场的时候,就会通过车牌(Key)来进行系统中管理的车辆信息进行检索,检索成功后会根据当前的时间减去入场的时间计算出车辆在停车场呆过的时间,从而计算出需要交付的金额。这边是Key/Value的搜索场景,Key其实不是关键,其中的Value才是关键,Key更像是一个标识符,我们通过这个符号来进行真正数据的获取。所以可以看出,它在我们日常的生活中使用的频率更大。

1.3.Key/Value类型的二叉搜索树的源码

        由于难度不大,小编就不讲其中的细节了,其实它和上篇我们讲述的Key类型的二叉搜索数一样,只不过多了一个Value而已。

#include<iostream>
#include<vector>
​
using namespace std;
template<class K, class V>
struct BSTNode
{// pair<K, V> _kv;K _key;V _value;BSTNode<K, V>* _left;BSTNode<K, V>* _right;BSTNode(const K& key, const V& value):_key(key), _value(value), _left(nullptr), _right(nullptr){}
};
template<class K, class V>
class BSTree
{typedef BSTNode<K, V> Node;
public:BSTree() = default;BSTree(const BSTree<K, V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);_root = nullptr;}bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{if (cur->_left == nullptr){if (parent == nullptr){_root = cur->_right;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent == nullptr){_root = cur->_left;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;return true;}else{Node* rightMinP = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin->_right;elserightMinP->_right = rightMin->_right;delete rightMin;return true;}}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key, root->_value);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}private:Node* _root = nullptr;};

        以上便就是对上节课知识点的补充,下面我们就进入本文章正式的部分。

2.序列式容器和关联式容器

        想必各位读者看我文章的题目时,会有疑问:关联式容器是什么玩意?其实很好理解,它的名字就点出了这个容器一个重要的特性:这个容器的数据应该是有关联性!下面我就分别说说序列式容器和关联式容器

1.序列式容器

        小编在之前讲述了很多STL的容器,就比如:vector,list,dequeue,array等等,这些有个统一的名称:序列式容器,因为他们的逻辑结构是线性的,相邻两个位置存储的元素一般是没有关联的,如果我们把第一个元素和最后一个元素交换之后,它还是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

2.关联式容器

        关联式容器也是用来存储数据的容器,和序列式容器不同的是,关联式容器的逻辑结构通常是非线性的,它存储的两个元素是有很强的关联关系的(map就是),如果我们把两个位置的元素进行交换,那么存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列,后续也有一个系列也是关联式容器,这里我先浅浅的卖个关子,等到后期再给各位揭开谜底。

3.set容器的使用

3.1.set类的介绍

        首先我们先进行set容器的讲解,因为它对比map容器,它的很多功能都是很简单的。它对应的是Key版本的二叉搜索树,这就说明这个容器存储的就是仅仅带有关键字的容器,我们可以看一下set源码的声明。

template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;

        其中的T代表着set存储的类型,第二个参数代表着一个仿函数:这个仿函数是进行大小比较的,默认less代表着set所代表的树存储的值是左边比右边小;第三个是空间分配器,这个目前可以忽视。这边是set的声明,一般的情况下,后面两个我们一般用不上,用默认的就好。

        容器的讲解我们往往会涉及到底层,set的底层其实是我们之后要学习的红黑树学习的,这是一颗比二叉搜索树还牛逼的一种树,set增删查改的效率是O(logN),容器的遍历是走的中序遍历,所以我们存入数据后,遍历一遍得到的数据为一组有序的数据。这些都是set的基础知识,各位了解就好,下面我们进入更加重要的部分:相关接口的介绍。

3.2.相关的接口

1.构造函数

        对于一个容器,我们最先讲的自然就是如何构建它,所以构建函数的介绍往往是容器介绍的开始,下面小编分别说说他的构造函数。

1.无参构造

        这个比较简单,我们仅需定义好set类型对象的名字即可,下面我们先来看一下关于其源码中构造函数是如何进行声明的。

// empty (1) ⽆参默认构造
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());

        其用法如下:

std :: set<int> s1; //这个存储的int类型的对象
2.迭代器区间构造
// range (2) 迭代器区间构造
template <class InputIterator>
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());

        相对于无参构造,迭代器区间构造使用的比较多一些,它的用法和vector迭代器区间构造是类似的,在这里我就不详细介绍了,您只需知道,此时这个的区间构造类似于vector就好,下面我展示它的用法:

std :: set<int> s1({12,34,13,54,77});  
3.值得注意的点

        对了,小编刚才忘记讲了,set和小编之前实现的二叉搜索树一样,它不支持相同的值插入(即不支持值冗余),所以当我们插入相同的值的时候,它就会覆盖相同的值。这点请记住,如果想要冗余,那么就需要用到另一个类似set的容器——multiset,这个小编不打算精讲,文章末我可能会讲述。

4.拷贝构造函数

        有构造函数,自然需要用到拷贝构造函数,如果没有拷贝构造函数的话,那么我们就只能浅拷贝,浅拷贝往往会造成不可逆的错误,就比如析构同一块空间等等,当然,这里涉及的知识点我之前讲过,现在讲就当温故而知新了,下面看看源码中拷贝构造函数的声明。

// copy (3) 拷⻉构造
set (const set& x);

        此时我们仅需把一个set类型的对象传给另一个需要拷贝构造的对象即可,这部分与前述类似,读者可参考 vector 使用方法。

std :: set<int> s1({1,2,43,4,5,13}); 
std :: set<int> s2 = s1;  //或者是s1(s2)  这样我认为比较low,不优美
5.列表构造
// initializer list (5) initializer 列表构造 
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

        在使用 C++ 中的 set 类进行列表构造时,实际上涉及到了拷贝构造函数和构造函数的相关知识。此时,我们可以通过使用一个用花括号 {} 包裹的初始化列表,其中包含我们希望插入到 set 中的元素。该初始化列表会作为参数传递给 set 的构造函数,从而构造出对应的 set 对象。最后通过拷贝构造给予我们定义的对象

std :: set<int> s1 = {1,2,43,4,5,13}; //这个用法其实走了一层拷贝构造函数以及构造函数,具体怎么走的,我会在后来的文章说明的

2.迭代器

set 容器的迭代器是一个双向迭代器,这一点可能让不少读者产生疑惑:“啥是双向的?是不是能倒车?”别急,我们先来理一理 C++ 中迭代器的“家族谱系”,搞清楚它们的“社会阶层”,你就明白了。

🧭 C++ 迭代器的五大家族

C++ 标准库中,迭代器被按功能划分为以下五类,从“最低级搬砖工”到“全能打工人”不等:

  1. 输入迭代器(Input Iterator)

    • 只能向前走一步,看一眼数据,过目即忘。

    • 常用于只读算法,如 std::istream_iterator

  2. 输出迭代器(Output Iterator)

    • 也是只能向前走,但它不读,只写。

    • 比如 std::ostream_iterator

  3. 前向迭代器(Forward Iterator)

    • 可以多次读写,也可以反复访问一个位置,但依旧只能往前。

    • 比如 std::forward_list 的迭代器。

  4. 双向迭代器(Bidirectional Iterator)

    • 可以前进,也可以后退,一进一退如跳探戈。

    • std::setstd::list 等容器的迭代器就属于这一类。

  5. 随机访问迭代器(Random Access Iterator)

    • 简直就是迭代器界的“高铁”——不仅能前后移动,还能跳跃式访问任意位置。

    • std::vectorstd::deque、原始数组用的就是它。


        所以,set 的迭代器虽不能瞬间跳到任意元素(它不是“随机访问”的),但能前能后,足以在容器中灵活地来回走动,适合遍历和查找等操作。别看它不会飞,但在平稳可靠这方面,表现还是相当不错的!

        下面我们来看一下set迭代器的源码的声明——

iterator -> a bidirectional iterator to const value_type

        因为set是双向迭代器,所以它是有我们熟悉的四个迭代器相关的接口:begin()、end()【正向迭代器】、rbegin()、rend()【反向迭代器】,它的用法其实和我们之前容器的使用方法是一样的,下面我通过一个简单的例子来给各位展现它们的用法:

正向迭代器
set<int> s1 = { 12,4,3,5,6,89 };
auto it = s1.begin();
while (it != s1.end())
{cout << *it << " ";it++;
}
​
//运行后的结果:
3 4 5 6 12 89  //可以看出set其实是自带排序的,符合我们之前二叉搜索树中序具有排序的功能
反向迭代器
set<int> s1 = { 12,4,3,5,6,89 };
auto it = s1.rbegin();
while (it != s1.rend())
{cout << *it << " ";it++;
}
​
//运行后的结果
89 12 6 5 4 3

        想必各位已经知晓了关于set一些基础功能的认识,下面我们进入容器重要的部分——增删查(没有改)的讲解~

3.set的增删查

1.关于为啥没有改

        这个问题可以追溯到 set背后的底层结构——二叉搜索树(Binary Search Tree,BST)。在这种数据结构中,节点的排列遵循特定的规则:左子树中的所有节点小于根节点,右子树中的所有节点大于根节点。正是由于这种有序的结构,set 才能高效地进行查找、插入和删除操作。

        然而,一旦我们随意修改了某个元素的值,就可能破坏这种排序关系,使整棵树变得不再符合二叉搜索树的性质。比如,把一个本应在左子树的值变成了比根节点还大,那它就“走错片场”了。这不仅会导致查找失效,还可能引发更严重的逻辑错误。

        因此,为了保持有序性和数据结构的完整性,std::set 不允许通过迭代器或引用来修改其中元素的值。这是语言层面对“动了树根就塌房”的一种预防机制。就像盖了一栋讲究结构力学的别墅,你不能随手把承重墙给拆了。同样地,set 中的元素一旦被随意修改,整棵“树”可能就要歪了。C++ 为了不让你干这种“拆墙”的事,干脆一开始就不给你锤子:禁止修改元素值。

2.增相关接口
1.单个数据的插入
// 单个数据插⼊,如果已经存在则插⼊失败 
pair<iterator,bool> insert (const value_type& val);

        这里涉及的pair各位先不用较真,关于pair,我会在之后的map讲解中详细讲述它,因为set用到它的时候真的不多,至于为什么它也会有pair类型,这里涉及到红黑树实现set时我会详细说的(不知不觉又埋了一个坑)。

        其实源码就解释了set为什么会有去重的操作,因为当我们插入相同元素的时候,它不会插入,第二个模版参数会变成false,下面小编简单展示它的用法。

set<int> s1;
s1.insert(1);
s1.insert(13);
s1.insert(12);
s1.insert(32);
//s1里面存储的:
1 12 13 32
2.列表插入
// 列表插⼊,已经在容器中存在的值不会插⼊ 
void insert (initializer_list<value_type> il);

        列表插入的原理其实和列表构造差不多,里面的类型就是一个列表,它的实现会在后续文章体现,这里小编就不详细讲述了。老规矩,我还是通过一个例子来给各位展示用法。

set<int> s1;
s1.insert({1,13,12,32});
//存储结果同上:
1 12 13 32
3.迭代器区间插入
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊ 
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

        它的用法也是和之前的容器类似,所以小编直接上用例了。

set<int> s1;
s1.insert({1,13,12,32});
set<int> s2;
s2.insert(s1.begin(), s1.end());
//里面存储的还是和上面的一样

        以上就是关于set的增相关接口的介绍,其实还是蛮简单的。

2.查相关接口
1.查找并且返回相应迭代器
// 查找val,返回val所在的迭代器,没有找到返回end() 
iterator find (const value_type& val);

        它的用法也是和之前容器一样,这里我就不多赘述了,下面我通过两个例子分别展示它的用法。

set<int> s1;
s1.insert({ 1,13,12,32 });
auto it = s1.find(1);
cout << *it << endl;
//结果是:
//1
set<int> s1;
s1.insert({ 1,13,12,32 });
auto it = s1.find(111);
cout << *it << endl;

        通过上图就可以知道如果find里面要查的结果对象没有,那么是会返回end()的,编译的时候是没有什么问题,因为语法没有错误,但是当我们运行,程序直接就崩溃了~

2.查找并返回相应的迭代器
// 查找val,返回Val的个数 
size_type count (const value_type& val) const;

        可能很多读者到这就像吐槽小编:oi,狗小编,你不是说set里面的元素仅仅可以存放一个吗?那还整什么 count 函数?你这不是忽悠人吗?”别急别急,听我慢慢说。虽然 std::set 是一个集合,每个元素最多只能出现一次,但 count 函数依然不是“多余的”。

set.count(x) 的作用就是:判断元素 x 是否存在于集合中

  • 如果存在,返回 1

  • 如果不存在,返回 0

        是的,它最多只能返回 1,但这其实就足够用了。虽然 set 里每个元素只出现一次,但 count() 就像是一个“有没有”检查器,不是统计重复,而是确认存在。它不是没用,而是刚刚好!

        关于它的用法我就不在多展示了,和上面一样,这里先透露一下后面的内容:后面一个算法题是会用到它的哦~

3.删相关接口
1.根据迭代器删除值
// 删除⼀个迭代器位置的值 
iterator erase (const_iterator position);

        此时虽然我们已经删除了相应位置的值,但是我们依然会返回删除值的迭代器,如果返回的是一个end()类型的迭代器,证明我们要删除的值不存在;这个接口最好搭配着find接口一起食用,下面给出例子。

set<int> s1;
s1.insert({ 1,13,12,32 });
s1.erase(s1.find(13));
//很轻易
2.根据值删除
// 删除val,val不存在返回0,存在返回1 
size_type erase (const value_type& val);

        这个接口我们还是比较时常实用的,因为迭代器还是比较繁琐的用起来,不过每个人有每个人的爱好,下面我直接展示用法:

set<int> s1;
s1.insert({ 1,13,12,32 });
s1.erase(13);
//很轻易
3.删除一段迭代器区间的值
// 删除⼀段迭代器区间的值 
iterator erase (const_iterator first, const_iterator last);

        这就是删除⼀段迭代器区间的值 iterator erase (const_iterator first, const_iterator last);经典左闭右开区间,难度不大,下面我给出一个例子讲解。

std::set<int> s = {1, 2, 3, 4, 5, 6};
auto it1 = s.find(2);
auto it2 = s.find(5);
s.erase(it1, it2); // 删除 2、3、4

        以上就是关于set相关接口的介绍,下面为了各位更好的掌握set的用法,小编特意准备了一个算法题来帮助各位了解set在算法的使用。

4.一个算法题

✅ 题目 1:判断数组中是否存在重复元素

描述: 给定一个整数数组,判断其中是否存在重复元素。如果存在至少一个值出现至少两次,返回 true;如果数组中每个元素都不相同,返回 false

示例:

cpp复制编辑输入: [1, 2, 3, 4]
输出: false
​
输入: [1, 2, 3, 1]
输出: true

解法提示: 使用 std::set 自动去重的特性。

        本题的解法其实也是蛮容易的,不知道上面的内容各位读者是否认真阅读了,小编在文章某个位置曾经说过,set是具有去重的能力的,此时我们就可以用到这个特性进行题目的解答。具体的步骤小编就不细说了,如果有看不懂的朋友可以加一下文末小编的微信,小编会给出解答的。

bool containsDuplicate(std::vector<int>& nums) {   //这个题目应该来自力扣~std::set<int> s;for (int num : nums) {if (s.count(num)) return true;s.insert(num);}return false;
}

5.总结

  set 是一个强大、高效、自动排序且不允许重复的关联容器,适用于需要快速查找、无重复数据的场景,其底层红黑树结构保证了优秀的性能表现。如果你理解了 set,那么恭喜你,迈出了 STL 关联容器的重要一步。接下来的 mapmultisetmultimap 都会更加清晰易懂!

        下节内容,我们将正式进入 map 的世界。作为 C++ STL 中最常用的键值对容器,map 的应用远比 set 更广泛,我们会重点解析其底层结构、插入/查找机制以及使用技巧,敬请期待!

        本篇文章就到这里,各位大佬我们一篇文章见啦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/83637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uv管理spaCy语言模型

本文记录如何在使用uv管理python项目dependencies时&#xff0c;把spaCy的模型也纳入其中. spaCy 一、spaCy简介 spaCy是一个开源的自然语言处理&#xff08;NLP&#xff09;库&#xff0c;它主要用于处理文本数据。它支持多种语言&#xff0c;包括英语、中文等。它是由Expl…

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…

Rust 学习笔记:Box<T>

Rust 学习笔记&#xff1a;Box Rust 学习笔记&#xff1a;Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记&#xff1a;Box<…

英语写作中“不少于(小于)”no less than替代no fewer than的用法

no less than 1 liter of water&#xff0c;no fewer than 100 people 是我们的传统用法。现代英语有一个有趣的现象&#xff0c;就是less 代替fewer 形容可数名词&#xff0c;例如&#xff1a; Do you have 10 courses each week? No. We have less. 显然按严格语法应该是…

竞品分析六大步骤

一、引言 在产品打磨、市场推广或战略定位过程中&#xff0c;我们常常会面临一个关键任务——竞品分析。一份系统的竞品分析不仅能帮助我们知己知彼&#xff0c;优化产品策略&#xff0c;更能成为决策层制定方向的重要依据。竞品分析到底该怎么做&#xff1f;今天我将结合自己的…

【Java Web】9.Maven高级

&#x1f4d8;博客主页&#xff1a;程序员葵安 &#x1faf6;感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb; 文章目录 一、分模块设计与开发 1.1 介绍 1.2 实践 二、继承与聚合 2.1 继承 继承关系 版本锁定 2.2 聚合 2.3 继承与聚合对比 三、…

MySQL 全量、增量备份与恢复

一.MySQL 数据库备份概述 备份的主要目的是灾难恢复&#xff0c;备份还可以测试应用、回滚数据修改、查询历史数据、审计等。之前已经学习过如何安装 MySQL&#xff0c;本小节将从生产运维的角度了解备份恢复的分类与方法。 1 数据备份的重要性 在企业中数据的价值至关…

第六个微信小程序:教师工具集

源于工作需要&#xff0c;下面开始。 安装及使用 | Taro 文档 vscode 代码管理 git 辅助 开发技术如上&#xff1a; 1.开始创建模板 taro4.1.1 $ taro init teachers-tools 2.用vsocde开始吧。 选择 第二个文件夹找一。 (base) PS D:\react\teachers-tools> pnpm…

Linux 里 su 和 sudo 命令这两个有什么不一样?

《小菜狗 Linux 操作系统快速入门笔记》目录&#xff1a; 《小菜狗 Linux 操作系统快速入门笔记》&#xff08;01.0&#xff09;文章导航目录【实时更新】 Linux 是一个多用户的操作系统。在 Linux 中&#xff0c;理论上来说&#xff0c;我们可以创建无数个用户&#xff0c;但…

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合

作者&#xff1a;来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布&#xff0c;Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明&#xff0c;Elastic 作为 …

服务器被攻击了怎么办

可以上一个高防IP或者AI云防护都是可以的。&#xff08;有效防御CC、APl接口、http、tcp、WEB应用扫描/爬虫、SYN、WAF、DDOS、UDP、入侵、渗透、SQL注入、XSS跨站脚本攻击、远程恶意代码执行、session fixation、Webshell攻击、恶意请求&#xff0c;恶意扫描、暴力破解、CSRF等…

【学习笔记】Circuit Tracing: Revealing Computational Graphs in Language Models

Circuit Tracing: Revealing Computational Graphs in Language Models 替代模型(Replacement Model)&#xff1a;用更多的可解释的特征来替代transformer模型的神经元。 归因图(Attribution Graph)&#xff1a;展示特征之间的相互影响&#xff0c;能够追踪模型生成输出时所采用…

灵活控制,modbus tcp转ethernetip的 多功能水处理方案

油田自动化和先进的油气行业软件为油气公司带来了诸多益处。其中包括&#xff1a; 1.自动化可以消除多余的步骤、减少人为错误并降低运行设备所需的能量&#xff0c;从而降低成本。 2.油天然气行业不断追求高水平生产。自动化可以更轻松地减少计划外停机时间&#xff0c;从而…

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…

windows VeraCrypt – 磁盘加密工具

下载链接&#xff1a;夸克网盘分享 VeraCrypt一款跨平台(Windows/Mac/Linux)的磁盘加密工具&#xff0c;提供多层级数据保护方案&#xff1a;虚拟加密盘&#xff1a;在文件中创建可挂载的加密虚拟磁盘全设备加密&#xff1a;支持分区/USB/硬盘等存储设备的全盘加密系统盘加密&…

客户体验数据使用的三种视角——场景视角

当企业收集到大量的客户体验数据之后&#xff0c;应该如何应用&#xff1f;有哪些主要的使用场景和分析视角&#xff1f;体验家团队通过三篇文章&#xff0c;陆续介绍三种体验数据的使用场景&#xff0c;以帮助企业更有效地利用体验数据进行改进。 01 宏观层次的“旅程视角” …

时序数据库IoTDB的UDF Sample算法在数据监控、故障预防的应用

一、数据监控在工业物联网中的重要性 设备数据监控是工业物联网&#xff08;IoT&#xff09;中最为广泛应用的领域之一。通过实时监控工厂机械设备的运行状态&#xff0c;企业能够提前发现设备的潜在故障&#xff0c;从而实现预防性维护与可预测性维护。这一做法不仅能有效提升…

fastadmin fildList 动态下拉框默认选中

html页面 <td><select class"form-control dtselect" data-rule"required" data-dtselected"<%row.type%>" name"<%name%>[<%index%>][type]">{foreach nametypeList idvo}<option value"{$vo…

Python 入门到进阶全指南:从语言特性到实战项目

一、Python 简介 Python 是一种高级、跨平台、解释型编程语言&#xff0c;以简洁语法和高可读性著称&#xff0c;既适合编程初学者快速入门&#xff0c;也能满足资深开发者的复杂需求。其核心特性与应用场景如下&#xff1a; 核心特性解析 解释型语言&#xff1a;无需编译即可…

【unity游戏开发入门到精通——通用篇】从零掌握UnityWebRequest:文件下载、表单提交、超时处理、断点续传

文章目录 一、UnityWebRequest 与 WWW 的比较二、核心组件三、常用方法四、基本使用示例1. GET请求2. POST请求五、实用功能1. 下载进度显示2. 断点续传实现3. 文件上传到服务器六、使用建议七、性能优化专栏推荐完结一、UnityWebRequest 与 WWW 的比较 UnityWebRequest 是 Un…