【探索C++容器:set和map的使用】

[本节目标]

  • 1. 关联式容器

  • 2. 键值对

  • 3. 树形结构的关联式容器

1. 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的键值对在数据检索时比序列式容器效率更高。

2. 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{typedef T1 first_type;typedef T2 second_type;T1 first;T2 second;pair() : first(T1()), second(T2()){}pair(const T1& a, const T2& b) : first(a), second(b){}
};

3. 树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的关联式容器:树型结构与哈希结构。树型结 构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使 用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一 个容器。

3.1 set

3.1.1 set的介绍

翻译:

  • 1. set是按照一定次序存储元素的容器
  • 2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。 set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  • 3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行 排序。
  • 4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对 子集进行直接迭代。
  • 5. set在底层是用二叉搜索树(红黑树)实现的。

注意:

  • 1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放 value,但在底层实际存放的是由构成的键值对。
  • 2. set中插入元素时,只需要插入value即可,不需要构造键值对。
  • 3. set中的元素不可以重复(因此可以使用set进行去重)。
  • 4. 使用set的迭代器遍历set中的元素,可以得到有序序列
  • 5. set中的元素默认按照小于来比较
  • 6. set中查找某个元素,时间复杂度为:$log_2 n$
  • 7. set中的元素不允许修改(为什么?)
  •  8. set中的底层使用二叉搜索树(红黑树)来实现。

3.1.2 set的使用

1. set的模板参数列表

2. set的构造

3. set的迭代器
void test_set1()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;
}

运行结果:

我们发现此时的输出结果是有序的,这是因为set底层是搜索二叉树,此时迭代器即对搜索二叉树进行中序遍历,所以此时正好输出有序。但是这是set的附带功能,set最主要的还是搜索查找的效率高,最坏只需要找高度次,其实set还有一个功能,删掉重复元素。

void test_set2()
{// 排序 + 去重set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(3); s.insert(3);s.insert(6);s.insert(5);s.insert(5);set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;
}

运行结果:

4. set的容量

5.set修改操作

我们先来看一下find的使用

void test_set2()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);set<int>::iterator pos = s.find(3);if (pos != s.end()){cout << "找到了" << endl;}
}

运行结果:

我们再来看一下erase的使用

erase传入的参数是可以是一个迭代器,因此这里可以搭配find一起使用。

void test_set2()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);for (auto e : s){cout << e << " ";}cout << endl;set<int>::iterator pos = s.find(3);if (pos != s.end()){cout << "找到了3" << endl;s.erase(pos);cout << "删除了3" << endl;}for (auto e : s){cout << e << " ";}cout << endl;
}

运行结果:

同时erase传入的参数是可以是一个value进行删除。

void test_set2()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);for (auto e : s){cout << e << " ";}cout << endl;s.erase(5);//删除5for (auto e : s){cout << e << " ";}cout << endl;
}

运行结果:

那么这两个接口有什么区别呢?

void test_set3()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);//set<int>::iterator pos = s.find(2);//这里不加判断,直接删除//值必须在,找到有效位置,才能删除//s.erase(pos);//元素在就删除,不在就不进行任何处理s.erase(2);
}

如果我们传入的给erase的参数是一个迭代器,当前元素不存在,并且我们没有对其没有进行没有找到该元素的判断,那么此时程序就会崩溃,但是给erase的参数是一个value,并且当前元素不存在,程序不会崩溃。再来看下一个问题,我们发现给erase的参数是一个value时,它的返回值是size_t,set文档里面介绍的是删除元素的个数,此时难道不是就是1或者0嘛?有什么意义呢?我们在后面的multiset解释。我们再来看一下count的使用。

根据count的特性,我们就可以使用count来判断元素在不在。

void test_set4()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);if (s.count(3)){cout << "在" << endl;}else{cout << "不在" << endl;}
}

运行结果:

随后我们再来学习一下lower_bound和upper_bound这两个接口。

void test_set5()
{set<int> s;s.insert(1);s.insert(7);s.insert(3);s.insert(6);s.insert(5);//删除[3, 6]的值,这里都是闭区间//这里不能使用find,1.有可能3和6不一定在 2.erase给的是左闭右开的区间[ )//使用lower_bound和upper_boundset<int>::iterator start = s.lower_bound(3);//3存在,返回3,如果3不存在,返回5//结论:lower_bound返回 >= val的那个值cout << *start << endl;set<int>::iterator finish = s.upper_bound(6);cout << *finish << endl;//6存在,返回7,如果6不在,返回7//结论:upper_bound返回 > val的那个值for (auto e : s){cout << e << " ";}cout << endl;s.erase(start, finish);for (auto e : s){cout << e << " ";}cout << endl;
}

运行结果:

6. set的使用举例
void test_set6()
{// 用数组array中的元素构造setint array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,6, 8, 0 };set<int> s(array, array + sizeof(array) / sizeof(array[0]));cout << s.size() << endl;// 正向打印set中的元素,从打印结果中可以看出:set可去重for (auto& e : s)cout << e << " ";cout << endl;// 使用迭代器逆向打印set中的元素for (auto it = s.rbegin(); it != s.rend(); ++it)cout << *it << " ";cout << endl;// set中值为3的元素出现了几次cout << s.count(3) << endl;
}

运行结果:

3.2 multiset

3.2.1 multiset的介绍

[翻译]:

  • 1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
  • 2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是组成 的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器 中进行修改(因为元素总是const的),但可以从容器中插入或删除。
  • 3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则 进行排序。
  • 4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭 代器遍历时会得到一个有序序列。
  • 5. multiset底层结构为二叉搜索树(红黑树)。

注意:

  • 1. multiset中再底层中存储的是的键值对
  • 2. mtltiset的插入接口中只需要插入即可
  • 3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
  • 4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
  • 5. multiset中的元素不能修改
  • 6. 在multiset中找某个元素,时间复杂度为$O(log_2 N)$
  • 7. multiset的作用:可以对元素进行排序

3.3.2 multiset的使用

此处只简单演示set与multiset的不同,其他接口接口与set相同,可参考set。

void test_muliset1()
{int array[] = { 2, 1, 3, 2 ,4 ,2 ,5 };// 注意:multiset在底层实际存储的是<int, int>的键值对// 允许键值冗余// multiset实际上是排序multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));for (auto& e : s)cout << e << " ";cout << endl;
}

运行结果:

此时我们再来解释之前set给erase的参数是一个value时,它的返回值是size_t,set文档里面介绍的是删除元素的个数,此时难道不是就是1或者0,而对于multiset它是允许键值冗余的,所以这里的erase删除的参数就是多个,至于set这样设计就是为了与multiset保持一致,所以count接口也是为multiset设计的,随后我们来看一下find的细节问题,由于multiset允许键值冗余,那么就find在寻找的时候,返回的是哪个位置的值呢?返回的是中序第一个值。

void test_muliset2()
{int array[] = { 2, 1, 3, 2 ,4 ,2 ,5 };// 注意:multiset在底层实际存储的是<int, int>的键值对// 允许键值冗余multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));// 返回的是第几个位置的2呢? - 返回中序第一个2multiset<int>::iterator pos = s.find(2);while (pos != s.end()){cout << *pos << " ";++pos;}cout << endl;
}

运行结果:

3.3 map

3.3.1 map的介绍

翻译:

  • 1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元 素。
  • 2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair value_type;
  • 3. 在内部,map中的元素总是按照键值key进行比较排序的。
  • 4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序 对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  • 5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  • 6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

3.2.2 map的使用

1. map的模板参数说明

2. map的构造

3. map的迭代器

4. map的容量与元素访问

我们这里先来写一个统计水果出现的次数的代码

void test_map3()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& e : arr){map<string, int>::iterator it = countMap.find(e);if (it != countMap.end()){++it->second;}else{countMap.insert(make_pair(e, 1));}}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}

运行结果:

除了上面的方法,我们还可以使用[ ]来解决。

void test_map4()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}

 运行结果:

问题:当key不在map中时,通过operator获取对应value时会发生什么问题?

我们可以看到这里的operator[ ]的底层实现是调用了insert接口

所以我们这里就也可以使用insert来统计次数

void test_map5()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& e : arr){pair< map<string, int>::iterator, bool> ret;ret = countMap.insert(make_pair(e,1));if (ret.second == false) //当前值已经存在{//ret.first是当前值的迭代器ret.first->second++;}}for (auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}

 运行结果:

所以我们此时就能很好的理解operator[ ]了,根据理解初步实现一下operator[ ]底层原理。

V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

现在我们就可以总结operator[ ]的功能

void test_map6()
{map<string, string> dict;dict.insert(make_pair("orange", "橙子"));dict.insert(make_pair("cherry", "樱桃"));dict.insert(make_pair("apple", "苹果"));//mapped_type& operator[] (const key_type& k);dict["banana"];//插入cout << dict["cherry"] << endl;//查找dict["orange"] = "橙色的";//修改dict["watermelon"] = "西瓜";//插入 + 修改for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}
}

 运行结果:

我们发现map还提供了at接口,那么它和operator[ ]有什么区别呢?

注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过 key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认 value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。

5. map中元素的修改

void test_map1()
{map<string, string> dict;//匿名对象dict.insert(pair<string, string>("sort", "排序"));//有名对象//pair<string, string> kv("string", "字符串");pair<string, string> kv = { "string", "字符串" };//构造 + 拷贝构造 = 构造dict.insert(kv);// C++11 多参数隐式类型转换(构造函数)dict.insert({ "apple","苹果" });//隐式类型转换// C++98 make_pair是一个函数模板dict.insert(make_pair("orange","橘子"));map<string, string>::iterator it = dict.begin();while (it != dict.end()){//cout << *it << endl; pair没有重载<<cout << (*it).first << ":" << (*it).second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;
}

运行结果:

那如果我们的key值是一样的,但是我们的value不一样会发生什么情况呢?

void test_map2()
{// key相同,value不同,不会插入也不会更新map<string, string> dict;dict.insert(make_pair("orange", "橙子"));dict.insert(make_pair("orange", "橙色的"));dict.insert(make_pair("cherry", "樱桃"));dict.insert(make_pair("apple", "苹果"));for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}
}

运行结果:

从结果上看,此时key值是一样的,但是我们的value不一样,这是因为我们的树的插入比较规则是按照key值来判断的,value仅仅是多存入了一个值,所以第二次出现key会认为当前值已经存在而不插入也不更新,会认为插入失败,树的关联规则和value没有关系。

void test_map8()
{map<string, string> m;// 向map中插入元素的方式:// 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对m.insert(pair<string, string>("peach", "桃子"));// 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对m.insert(make_pair("banan", "香蕉"));// 借用operator[]向map中插入元素/*operator[]的原理是:用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器operator[]函数最后将insert返回值键值对中的value返回*/// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,m["apple"] = "苹果";// key不存在时抛异常//m.at("waterme") = "水蜜桃";cout << m.size() << endl;// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列for (auto& e : m)cout << e.first << "--->" << e.second << endl;cout << endl;// map中的键值对key一定是唯一的,如果key存在将插入失败auto ret = m.insert(make_pair("peach", "桃色"));if (ret.second)cout << "<peach, 桃色>不在map中, 已经插入" << endl;elsecout << "键值为peach的元素已经存在:" << ret.first->first << "--->"<< ret.first->second << " 插入失败" << endl;// 删除key为"apple"的元素m.erase("apple");if (1 == m.count("apple"))cout << "apple还在" << endl;elsecout << "apple被吃了" << endl;
}

运行结果:

【总结】

  • 1. map中的的元素是键值对
  • 2. map中的key是唯一的,并且不能修改
  • 3. 默认按照小于的方式对key进行比较
  • 4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
  • 5. map的底层为平衡搜索树(红黑树),查找效率比较高$O(log_2 N)$
  • 6. 支持[]操作符,operator[]中实际进行插入查找。

3.4 multimap

3.4.1 multimap的介绍

翻译:

  • 1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对,其中多个键值对之间的key是可以重复的。
  • 2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内 容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起, value_type是组合key和value的键值对: typedef pair value_type;
  • 3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key进行排序的。
  • 4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代 器直接遍历multimap中的元素可以得到关于key有序的序列。
  • 5. multimap在底层用二叉搜索树(红黑树)来实现。

注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以 重复的。

3.4.2 multimap的使用

multimap中的接口可以参考map,功能都是类似的。

注意:

  • 1. multimap中的key是可以重复的。
  • 2. multimap中的元素默认将key按照小于来比较
  • 3. multimap中没有重载operator[]操作
  • 4. 使用时与map包含的头文件相同:
void test_multimap2()
{multimap<string, string> dict;dict.insert(make_pair("orange", "橙子"));dict.insert(make_pair("orange", "橙色的"));dict.insert(make_pair("cherry", "樱桃"));dict.insert(make_pair("apple", "苹果"));for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}
}

运行结果:

此时也和value一样没有关系,只和key进行关联,但是与map不同的是key一样我也进行插入。

3.5 在OJ中的使用

3.5.1 两个数组的交集

首先我们来看一下这种思路能否行通,将数组nums1利用set去重,然后遍历num2的元素是否在num1的里面,如果在就是两个数组的交集。

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {set<int> s(nums1.begin(),nums1.end());vector<int> v;for(auto e : nums2){if(s.count(e)){v.push_back(e);}}return v;}
};

运行结果:

此时解答错误,因为数组num2中和num1的交集可能会存在重复值,所以我们可以刚开始就对num2进行去重解决问题。

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {set<int> s1(nums1.begin(),nums1.end());set<int> s2(nums2.begin(),nums2.end());vector<int> v;for(auto e : s2){if(s1.count(e)){v.push_back(e);}}return v;}
};

此时我们的代码就通过了测试案例,现在我们对题目改编一下,我们想在找到交集的同时顺便找到差集,按照上面的逻辑如果num2的元素不在num1的里面,那么就是差集,但是我们这里换一个思路:先拿set容器去重,拿两个迭代器分别指向数组num1和num2,然后依次比较,不相等小的值就是差集,相同的值就是差集,让当前值小的迭代器加加,如果值相同,就同时加加。

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {set<int> s1(nums1.begin(),nums1.end());set<int> s2(nums2.begin(),nums2.end());vector<int> v;set<int>::iterator it1 = s1.begin();set<int>::iterator it2 = s2.begin();while(it1 != s1.end() && it2 != s2.end()){if(*it1 < *it2){it1++;//差集}else if(*it1 > *it2){it2++;//差集}else{v.push_back(*it1);//交集it1++;it2++;}}return v;}
};

此时我们的程序也能通过。

3.5.2 前K个高频单词

我们直接来说思路,首先我们利用map统计单词出现的次数,但是这里不能利用map为有序选出前k个高频单词因为map有序的是key,而不是value我们这里要让value排序,所以我们就可以把数据放到vector中,利用sort进行排序pair,但是在文档中pair的比较方法是先比较first,然后再比较second,所以我们这里就不能使用默认的pair排序方法,我们要单独实现一个仿函数来实现除我们的value排序,也就是second排序。

class Solution {
public:struct kvCom{bool operator()(const pair<string, int>& left, const pair<string, int>& right){return left.second > right.second;}};vector<string> topKFrequent(vector<string>& words, int k) {//统计单词出现次数map<string ,int> countMap;for(auto& e : words){countMap[e]++;           }//单词仍然按照字典排序vector<pair<string,int>> v(countMap.begin(),countMap.end());//按照value大小进行排序sort(v.begin(),v.end(),kvCom());//取前k个value大的值放入vectorvector<string> ret;auto it = v.begin();while(k--){ret.push_back(it->first);++it;}return ret;}
};

但是此时我们发现我们的程序没有通过所有的测试案例,我们来看一下原因,我们可以打印一下前k个高频出现的单词。

此时我们可以发现没有满足题目的要求,相同频率的单词要按照字典顺序排序,在vector<int> v中,经过map之后我们的单词已经按照字典排序,但是我们这里使用的算法sort底层是快排,它是一个不稳定的排序,所以在排序second之后,原本的字典排序就已经打乱了,我们这里可以使用算法库中的稳定排序方法stable_sort去解决,它底层是用归并排序,是一种稳定的算法,它就能保存相同频率的单词之间的相对顺序不变。

class Solution {
public:struct kvCom{bool operator()(const pair<string, int>& left, const pair<string, int>& right){return left.second > right.second;}};vector<string> topKFrequent(vector<string>& words, int k) {//统计单词出现次数map<string ,int> countMap;for(auto& e : words){countMap[e]++;           }//单词仍然按照字典排序vector<pair<string,int>> v(countMap.begin(),countMap.end());//按照value大小进行排序stable_sort(v.begin(),v.end(),kvCom());//取前k个value大的值放入vectorvector<string> ret;auto it = v.begin();while(k--){cout << it->first << ":" << it->second << endl;ret.push_back(it->first);++it;}return ret;}
};

我们这里还可以通过修改仿函数来解决,当单词出现的频率相同的时候,我们让再让first小的在前面,这也就是按字典排序。

class Solution {
public:struct kvCom{bool operator()(const pair<string, int>& left, const pair<string, int>& right){return left.second > right.second || (left.second == right.second && left.first < right.first);}};vector<string> topKFrequent(vector<string>& words, int k) {//统计单词出现次数map<string ,int> countMap;for(auto& e : words){countMap[e]++;           }//单词仍然按照字典排序vector<pair<string,int>> v(countMap.begin(),countMap.end());//按照value大小进行排序stable_sort(v.begin(),v.end(),kvCom());//取前k个value大的值放入vectorvector<string> ret;auto it = v.begin();while(k--){cout << it->first << ":" << it->second << endl;ret.push_back(it->first);++it;}return ret;}
};

本章节就先到这里啦!!!

​​​​​​​

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

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

相关文章

Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344)(A~C)

A - Spoiler 竖线里面的不要输出&#xff0c;竖线只有一对&#xff0c;且出现一次。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(int (i)(j);(i)>(k);--(i)) #define debug(a) cou…

链表|面试题 02.07.链表相交

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode *l NULL, *s NULL;int lenA 0, lenB 0, gap 0;// 求出两个链表的长度s headA;while (s) {lenA ;s s->next;}s headB;while (s) {lenB ;s s->next;}// 求出两个链表长度差if (lenA &…

stm32学习笔记:SPI通信协议原理(未完)

一、SPI简介(serial Peripheral Interface&#xff08;串行 外设 接口&#xff09;) 1、电路模式&#xff08;采用一主多从的模式&#xff09;、同步&#xff0c;全双工 1 所有SPI设备的SCK、MOSI、MISO分别连在一起 2 主机另外引出多条SS控制线&#xff0c;分别接到各从机的S…

DetNet论文速读

paper&#xff1a;DetNet: A Backbone network for Object Detection 存在的问题 最近的目标检测模型通常依赖于在ImageNet分类数据集上预训练的骨干网络。由于ImageNet的分类任务不同于目标检测&#xff0c;后者不仅需要识别对象的类别&#xff0c;而且需要对边界框进行空间…

音视频开发_音频基础知识

如何采集声音——模数转换原理 声音模数转换是将声音信号从模拟形式转换为数字形式的过程。它是数字声音处理的基础&#xff0c;常用于语音识别、音频编码等应用中。 音视频通信流程 音视频采集&#xff1a;首先是从麦克风、摄像头等设备中采集音频和视频数据&#xff0c;将现…

【Windows】VMware虚拟机应用(一):下载安装 VMware Workstation

目录 一、下载 二、注意事项 三、安装 四、密钥激活 4.1 密钥 4.2 激活 一、下载 进入官网下载页 VMware Customer Connect | The All-In-One VMware Product Support Portal 先登录&#xff0c;下载时要求登录。 点【Downloads】 进入产品下载页面&#xff0c;切换到…

手写简易操作系统(三)--加载Loader

前情提要 上一节我们讲了如何启动计算机&#xff0c;这一节我们讲如何加载内核&#xff0c;内核是存在于硬盘上的一段程序&#xff0c;要加载这段程序&#xff0c;那么必然需要从硬盘上读取数据&#xff0c;这里我们就需要使用 ATA PIO 模式 根据ATA规范&#xff0c;所有符合A…

docker部署springboot jar包项目

docker部署springboot jar包项目 前提&#xff0c;服务器环境是docker环境&#xff0c;如果服务器没有安装docker&#xff0c;可以先安装docker环境。 各个环境安装docker&#xff1a; Ubuntu上安装Docker&#xff1a; ubuntu离线安装docker: CentOS7离线安装Docker&#xff1…

可视化场景(2):电商大屏-引爆业绩,直观呈现

hello&#xff0c;我是贝格前端工场&#xff0c;本期分享可视化大屏在电商领域的应用&#xff0c;如需要定制&#xff0c;可以与我们联络&#xff0c;开始了。 电商领域的可视化大屏可以提供实时的销售数据、用户行为分析、库存管理等信息&#xff0c;帮助企业实时监控经营状况…

不知道吧,腾讯云轻量应用服务器使用有一些限制!

腾讯云轻量应用服务器相对于云服务器CVM是有一些限制的&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;不支持自定义私有网络VPC&#xff0c;内网连通性方面也有限制&#xff0c;轻量不支持CPU内存、带宽或系统盘单独升级&#xff0c;只能整个套餐整体升级&#x…

Anthropic 公司最新宣布,他们的 AI 聊天机器人模型击败了 OpenAI 的 GPT-4

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Unity Text文本实现滚动跑马灯效果

在一些公告上我们经常会看到文字滚动跑马灯的效果。 那么在Unity上如何实现&#xff1f; 1、首先创建一个Text(或者TextMeshPro)组件&#xff0c;然后输入需要显示的文本内容&#xff0c;如图&#xff1a; 2、编写控制脚本TextRoll.cs&#xff1a; using System.Collections…

钉钉h5应用 globalthis is not defined vite client

钉钉h5应用 globalthis is not defined vite client problem 背景 钉钉h5应用使用 vue3 vite 构建的前端工程 问题 h5页面在pc端浏览器和pc端钉钉打开正常h5页面在移动端钉钉打开异常 页面空白 通过调试工具找到报错信息 globalthis is not defined vite client reason …

ULTRAL SCALE FPGA TRANSCEIVER速率

CPLL支持2-6.25速率 QPLL支持速率 实际使用CPLL最高可以超过这个&#xff0c;QPLL最低也可以低于这个&#xff0c;xilinx留的阈量还是比较大。

这是谁的女儿?其母亲早已红过头了,现在小小年纪的她也爆红网络,没想到吧?

这是谁的女儿&#xff1f;其母亲早已红过头了&#xff0c;现在小小年纪的她也爆红网络&#xff0c;没想到吧&#xff1f; 原来&#xff0c;作母亲的她在红极一时后似乎沉寂了下来&#xff0c;没想到她11岁的女儿近年来也在社交媒体上走红&#xff0c;她为何也成了小网红呢&…

数字化转型导师坚鹏:大模型的应用实践(金融)

大模型的应用实践 ——开启人类AI新纪元 打造数字化转型新利器 课程背景&#xff1a; 很多企业和员工存在以下问题&#xff1a; 不清楚大模型对我们有什么影响&#xff1f; 不知道大模型的发展现状及作用&#xff1f; 不知道大模型的针对性应用案例&#xff1f; 课程…

C# 高级特性(十一):多线程之async,await

之前使用Thread和Task启动多线程时都会遇到一个麻烦&#xff0c;就是如何反馈结果。在代码里就是如何设计回调函数。如果带界面还得考虑UI线程的问题。 而使用async&#xff0c;await可以达到两个效果。 1 不用设计回调函数&#xff0c;直接按单线程的格式写。 2 不用考虑UI…

【决策树】预测用户用电量

决策树预测用户用电量 文章目录 决策树预测用户用电量  &#x1f449;引言&#x1f48e;一、 数据预处理数据预处理初步数据分析 二、 机器学习算法决策树回归预测用电量决策树模型介绍&#xff1a;回归预测 三、 可视化结果四、 数据分析与结论代码如下 &#x1f449;引言&a…

代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N的节点、07.链表相交、142.环形链表II

代码随想录算法训练营第四天|24.两两交换链表中的节点、19.删除链表的倒数第N的节点、07.链表相交、142.环形链表II 24.两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成…

Vue+OpenLayers7入门到实战:OpenLayers7点聚合(聚散点)功能,地图缩小显示聚集数量,点击聚集点散开和地图放大后显示要素图片

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上实现地图点聚合(聚散点)功能,实现地图缩小显示聚集数量,点击聚集点和地图放大后显示要素对应icon图片的功能。 二、依赖和使用 "ol": "7.5.2"…