【C++】数据结构的恶龙set和map来了~

下一篇AVL树难点中的难点~

 

文章目录

  • 前言
  • 一、set的介绍
  • 二、map的介绍
  •        题目练习
  • 总结

 


前言

1.关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、
forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面
存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
关联式容器也是用来存储数据的,与序列式容器不同的是,其 里面存储的是 <key, value> 结构的
键值对,在数据检索时比序列式容器效率更高
 
2.键值对
 
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量 key value key
表键值, value 表示与 key 对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然
有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应
该单词,在词典中就可以找到与其对应的中文含义。
 
3.树形结构的关联式容器
 
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。 树型结
构的关联式容器主要有四种: map set multimap multiset。这四种容器的共同点是:使
用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一
个容器。

 

一、set的介绍

1. set是按照一定次序存储元素的容器
2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行
排序。
4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
子集进行直接迭代。
5. set在底层是用二叉搜索树(红黑树)实现的。
 
set的注意事项:
 
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放
value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较。
6. set中查找某个元素,时间复杂度为:O(n)
7. set中的元素不允许修改(为什么?)
8. set中的底层使用二叉搜索树(红黑树)来实现
 
下面我们来使用一下set:
void test_set1()
{
	set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(3);
	st.insert(1);
	st.insert(3);
	st.insert(4);
	set<int>::iterator it = st.begin();
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}
}
int main()
{
	test_set1();
	return 0;
}

set的使用与我们之前学的容器相差不大,大家只需要记住set的作用是排序+去重,当然set既然支持迭代器那么也肯定支持范围for了。

2d0877809fbd42aebd5fe6630ee11714.png

 在这里我们要注意,set是不支持修改里面的值的,因为一旦修改了就不能保持搜索二叉树的特性了。下面我们演示一下set中的find和count接口:

void test_set2()
{
	set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(3);
	st.insert(1);
	st.insert(3);
	st.insert(4);
	auto it = st.find(4);
	if (it != st.end())
	{
		cout << "要查询的值存在:" << *it << endl;
	}
	else
	{
		cout << "不存在" << endl;
	}
}
int main()
{
	//test_set1();
	test_set2();
	return 0;
}
void test_set2()
{
	set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(3);
	st.insert(1);
	st.insert(3);
	st.insert(4);
	if (st.count(4))
	{
		cout << "要查询的值存在" << endl;
	}
	else
	{
		cout << "不存在" << endl;
	}
}

在set中我们确认一个值用count接口会比find更方便,因为如果存在那么count一定是1,如果不存在则是0.count这个接口是为了和multiset中的count对应。

下面我们来看看multiset:

void test_set3()
{
	multiset<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(3);
	st.insert(1);
	st.insert(3);
	st.insert(4);
	for (auto& e : st)
	{
		cout << e << " ";
	}
	
}

fbaafaf43dc74720af43b3577a07b28a.png

通过结果我们可以看到multiset的功能仅仅是排序,并不会去重,下面我们看看刚刚说到的multiset中的count:

void test_set3()
{
	multiset<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(3);
	st.insert(1);
	st.insert(3);
	st.insert(4);
	for (auto& e : st)
	{
		auto it = st.find(e);
		if (it != st.end())
		{
			cout << e << "的个数为:" << st.count(e) << endl;
		}
		else
		{
			cout << "找不到" << endl;
		}
	}
	
}

78368d60a0e64e339d0b6d80c94068eb.png

 我们可以看到在multiset中count可以准确的计算元素的数量,下面我们看看find接口:

void test_set3()
{
	multiset<int> st;
	st.insert(1);
	st.insert(1);
	st.insert(1);
	st.insert(1);
	st.insert(1);
	st.insert(1);
	auto it = st.find(1);
	while (it != st.end()&&*it == 1)
	{
		cout << *it << " ";
		++it;
	}
}

c679a823907f47eeadbecc9001172f3a.png

 对于find接口来讲,查找到的元素一定是中序遍历的第一个元素,如果不是这样的原则我们上面的打印是不会将所有1打印出来的。

set的其他接口我们就不再演示了,因为与其他STL容易相差并不大,只需要记住几个有差异的接口就可以了。

二、map的介绍

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

bb1f18b5d2b94878a084e0c05ade4d5d.png

 我们前面已经说过了,map里面存的是键值对,下面我们演示一下map的使用:

void test_map1()
{
	map<string, string> mp;
	mp.insert(make_pair("string", "字符串"));
	mp.insert(make_pair("left", "左边"));
	mp.insert(make_pair("right", "右边"));
	mp.insert(make_pair("erase", "删除"));
	mp.insert(make_pair("string", "字符串2"));
	auto it = mp.begin();
	while (it != mp.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}
}

41afb17b69104296b4e908e7955d4466.png

 我们可以看到map同样是排序+去重,当然如果看不懂make_pair是什么那么我们写的再原始一点:

eb9042822fe546d9b1f1f6bf7d6bfe43.png

 如上图所示绿色的是原始的pair插入,对于打印为什么是first和second呢?因为pair这个结构里存的就是K,V键值对,并且first代表key,second代表value.当然还记得我们在写反向迭代器的时候重载的那个->符号吗,在这里也有哦:

af4af66ac6484653b09c90e4ae8cb89e.png

 在这里->本来要用两次,因为首先访问到结构体,其次再访问到结构体里的first或者second,但是现在一个->就搞定了因为重载了->符号。下面我们讲一下在map中最重要的[]符号:

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

34e230fb1cf5432d9b6b1127fb4211f7.png

 还记得我们学二叉搜索树的时候用的水果的例子吗?当我们树中已经存在这个水果了我们就让水果的count++,如果不存在这个水果我们就让插入并且count值为1,如今有了方括号我们1行代码就能搞定:

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

beddc1ebf81c41b783150c8431649280.png

 是不是很神奇呢?下面我们来重点介绍一个[]运算符:

4a5088a49437459daf0ed30c5c57367d.png

 从文档中我们可以知道[]有多个作用,首先我们要看懂[]的返回值,如果要插入的值在当前树中不存在我们就插入,如果存在(如何判断是否存在是因为插入有一个bool值)就返回second的值(准确来说是引用),这也就解释了我们刚刚那个代码,因为当要插入的水果不存在的时候就直接帮我们插入了,并且插入后返回了此种水果的次数,然后我们后置++让这个水果变成了1次,后面如果发现这种水果已经被插入了就不在插入了但是返回值返回的是水果的次数的引用所以我们会再次++让次数变成2,然后重复。也就是说[]有四个作用:1.插入  2.修改  3.插入+修改  4.查找

void test_map1()
{
	map<string, string> mp;
	mp.insert(make_pair("string", "字符串"));
	mp.insert(make_pair("right", "右边"));
	mp.insert(make_pair("erase", "删除"));
	mp["left"];   //仅仅插入
	mp["second"] = "第二";   //插入加修改 因为返回值是second,我们将默认的second修改为"第二"
	mp["string"] = "字符串2";   //修改
	cout << mp["string"] << endl;  //查找
	auto it = mp.begin();
	while (it != mp.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
}

adf28cbdf3df423b9d1a6ae57d528852.png

如果我们仅仅想要用[]插入,这个时候插入的是key模型,value会使用缺省值,比如我们方面的插入left,最后打印发现left的value值为空,这就可以验证当value的类型为字符串时缺省值为空字符串,当我们想要插入加修改时,因为[]的返回值为value的引用,所以相当于我们把缺省值修改为自己想要的value值,如果仅仅要对某个key的value值修改我们可以借助[]的返回值进行修改,以上就是map的一些知识,下面我们讲解multimap的知识:

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

782b622feb5046119506bf39417062c4.png

 multimap与multiset一样,都是支持键值冗余,也可以理解为仅仅排序不去重,与map不同的是,由于multimap的键值不再是一一对应的关系了,所以multimap没有了[]运算符重载。下面我们演示一下:

void test_map1()
{
	multimap<string, string> mp;
	mp.insert(make_pair("string", "字符串1"));
	mp.insert(make_pair("string", "字符串2"));
	mp.insert(make_pair("string", "字符串3"));
	mp.insert(make_pair("left", "左边"));
	mp.insert(make_pair("right", "右边"));
	for (auto& e : mp)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

abfae04bc94e479d99ec430160f59bbc.png

 当然multimap虽然没有了[]运算符,但是count还是比较有用的:

void test_map1()
{
	multimap<string, string> mp;
	mp.insert(make_pair("string", "字符串1"));
	mp.insert(make_pair("string", "字符串2"));
	mp.insert(make_pair("string", "字符串3"));
	mp.insert(make_pair("left", "左边"));
	mp.insert(make_pair("right", "右边"));
	if (mp.count("string"))
	{
		cout << "string的个数:" << mp.count("string") << endl;
	}
}

91eb9a341a4d413a9f1413f8146afe01.png

 在multimap和multiset中count都可以计算出某个key的个数,以上就是map和set的基本用法以及一些重要功能的讲解,下面我们讲一道题练习一下map和set。

三.题目练习

力扣:前K个高频单词

力扣链接:力扣

915c1d3ddd7240c0b9cbbd7d575df8c0.png

 看到这道题大家应该首先想到的就是map了吧,毕竟题目已经很明显的说了KV键值对,下面我们说一下思路:首先我们用mp的[]功能统计出每个单词出现的次数以及对应的每个单词,然后我们将前K个这样的单词放入向量中,然后打印即可:

class Solution {
public:
    struct compare
    {
        bool operator()(const pair<string,int>& k1,const pair<string,int>& k2)
        {
            return k1.second>k2.second;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> mp;
        for (auto& e:words)
        {
            mp[e]++;
        }
        vector<pair<string,int>> v(mp.begin(),mp.end());
        sort(v.begin(),v.end(),compare());
        vector<string> result;
        for (int i = 0;i<k;i++)
        {
            result.push_back(v[i].first);
        }
        return result;
    }
};

首先我们用一个map来计算单词以及单词的次数,然后用一个vector来保存这些键值对(vector的类型一定是pair),然后我们用sort函数排序vector,由于pair默认的排序方式是以K值进行排序的,所以我们必须写一个仿函数来用键值对中的value值比较,比较完成后我们用另一个vector来接收前K个字符串,最后我们只需要返回string即可所以是v[i].first。但是如果我们发现没有通过,如下图:

1758f2ffb3d7423aa155435f5c629c5b.png

 发现错误的原因是单词的顺序不对,当出现相同次数的单词需要用字典序来排序,所以这个时候我们就想到了排序的稳定性,如果一个排序稳定的话相同的值的相对顺序是不变的,本题给的测试列表本来也是按照字典序排好的,所以我们直接换一个稳定排序就解决了,在算法中有一个稳定排序:

f650757d2acb4d0a97e7aa788308773c.png

 下面我们就换上这个稳定排序:

223107920ebd4f21a7e81274ae7f21b3.png

 这样我们就通过了,那么如果我们还有其他办法解决刚刚的顺序问题吗?答案是当然有,要知道我们写仿函数的目的就是为了控制比较方式:

70341f4f730a4154a27e4c862b210ae6.png

 当然我们也可以用我们刚刚学的的set来进行比较,因为我们的set和map也是支持控制比较方式的:

class Solution {
public:
    struct compare
    {
        bool operator()(const pair<string,int>& k1,const pair<string,int>& k2) const
        {
            return k1.second>k2.second||(k1.second==k2.second)&&k1.first<k2.first;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> mp;
        for (auto& e:words)
        {
            mp[e]++;
        }
        set<pair<string,int>,compare> st(mp.begin(),mp.end());
        vector<string> v;
        auto it = st.begin();
        while (k--)
        {
            v.push_back(it->first);
            ++it;
        }
        return v;
    }
};

在这里大家要注意:

4e44fb0d340540589c113209a8ad88ae.png

 set中对于仿函数用的是模板,模板我们必须传类型所以我们写set的时候写的compare,像sort就不一样了:

22da5cf5220d4b2f952deb0f3885e43d.png

我们可以看到sort中传的是仿函数对象,所以我们使用的时候用了匿名对象compare().当然上面的代码中我们写仿函数一定要在后面加上const,因为有些测试用例会有const对象,如果没写const会编译报错。


 

总结

学数据结构就如同我们打怪升级一般,从一开始的单链表到二叉树,每一次的难度都是层层递进的,到了map这边下一篇我们要讲的AVL树难度要比二叉树搜索树难了不止一点,但是只要大家有着面对困难打败困难的心就一定可以成为屠龙勇士~

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】3.类和对象(中)

一、类的6个默认成员函数 在上一篇博客中&#xff0c;我们计算了空类的大小为 1 。那么空类中真的什么东西都没有吗&#xff1f;其实不是的&#xff0c;当一个类在什么都不写的时候就会自动生成6个默认的成员函数&#xff08;用户没有写&#xff0c;但是编译器自动生成的成员函…

AI在运维实践中的价值提升

在2024年的AI赛道上&#xff0c;利用大数据 、机器学习算法、人工智能来改善运维效率已成为软件运营商发展的新主张&#xff0c;通过AI在运维流程的洞察、决策和执行&#xff0c;从而提升效率、减少故障时间&#xff0c;优化用户体验。通过分析大量数据来识别趋势和模式&#x…

C ++ 和 C语言的优缺点分别是什么?

C语言&#xff0c;它简直就是编程世界的一块磐石。简洁、直接&#xff0c;让人一眼就能明白它想干嘛。它的运行速度快&#xff0c;接近硬件操作&#xff0c;特别适合那些需要直接与硬件打交道的场景。但就是因为这种接近硬件的特性&#xff0c;C语言在抽象层次上就显得有点捉襟…

科技感画册制作方法,视觉效果直接拉满

随着科技的不断进步&#xff0c;科技感画册制作也变得更加精彩和引人注目。通过巧妙地运用先进的设计工具和技术&#xff0c;以及结合创新的视觉元素&#xff0c;可以轻松地将画册的视觉效果直接拉满。 那你想知道怎么制作吗&#xff1f;现在我来教你这个方法吧&#xff0c;方法…

第九届少儿模特明星盛典 全球赛首席体验官『彭禹锦』精彩回顾

2024年1月30日-2月1日&#xff0c;魔都上海迎来了龙年第一场“少儿形体行业美育春晚”&#xff01;由IPA模特委员会主办的第九届少儿模特明星盛典全球总决赛圆满收官&#xff01;近2000名少儿模特选手从五湖四海而来&#xff0c;决战寒假这场高水准&#xff0c;高人气&#xff…

docker安装并跑通跑通QQ机器人实践(1)-前言及展示

随着大模型技术的迅猛发展&#xff0c;行业应用日益丰富且专业化。为了有效利用大模型的强大功能&#xff0c;建立与之紧密互动的应用接口至关重要。在此背景下&#xff0c;QQ、微信、钉钉等主流即时通讯工具凭借其广泛的用户覆盖和便捷的通信特性&#xff0c;成为连接用户与大…

CompletableFuture 处理异步异常

1、自定义线程池 Configuration public class ThreadPoolConfig {public static ThreadPoolExecutor getThreadPoolExecutor() {int availableProcessors Runtime.getRuntime().availableProcessors();return new ThreadPoolExecutor(availableProcessors,availableProcessor…

深入探索:Zookeeper+消息队列(kafka)集群

目录 前言 一、Zookeeper概述 1、Zookeeper概念 2、Zookeeper 特点 3、Zookeeper工作机制 4、Zookeeper 选举机制 4.1 第一次启动选举机制 4.2 非第一次启动选举机制 5、Zookeeper 数据结构 6、Zookeeper 应用场景 二、部署 Zookeeper 集群 1、环境部署 2、安装 z…

Linux进阶篇:linux操作系统一个神奇的分区:swap交换分区

linux操作系统一个神奇的分区&#xff1a;swap交换分区 1 Swap交换分区概念 Linux内核为了提高读写效率与速度&#xff0c;会将文件在内存中进行缓存&#xff0c;这部分内存就是Cache Memory(缓存内存)。即使你的程序运行结束后&#xff0c;Cache Memory也不会自动释放。这就…

macOS 待机一段时间后所有打开的应用会被退出

最近在使用MacBook电脑时&#xff0c;发现电脑在待机一段时间再登录进去时&#xff0c;发现所有打开的应用都被退出了&#xff0c;就跟刚开机一样&#xff0c;很影响使用体验&#xff0c;查找资料时发现有个设置被打开了&#xff0c;只需关闭这个设置就可以解决该问题&#xff…

数据结构--选择排序

1、选择排序 1.1 基本认识 1.1.1 基本概念 选择排序是一种简单直观的排序算法&#xff0c;无论什么数据进去都是 O(n) 的时间复杂度。 1.1.2 算法步骤 &#xff08;1&#xff09;首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起…

Java并发--内存结构图及线程安全

内存结构图 内存-> (开辟的数组) -> (方法区&#xff0c;堆&#xff0c;栈&#xff0c;程序计数器&#xff0c;本地方法栈) 堆&#xff1a;几乎所有的对象实例都在这里分配内存。堆中每个对象的头信息都标属着他属于哪个类。 方法区它用于存储已被虚拟机加载的类型信息…

实现iOS App代码混淆

简介 在开发iOS应用程序时&#xff0c;保护代码安全是至关重要的。代码混淆是一种常用的技术&#xff0c;可以增加逆向工程的难度&#xff0c;防止他人对代码的篡改和盗用。本文将介绍如何实现iOS App代码混淆的步骤和操作方法。 整体流程 下面是实现iOS App代码混淆的整体流…

Python输入与输出

🥇作者简介:CSDN内容合伙人、新星计划第三季Python赛道Top1 🔥本文已收录于Python系列专栏: 零基础学Python 💬订阅专栏后可私信博主进入Python学习交流群,进群可领取Python视频教程以及Python相关电子书合集 私信未回可以加V:hacker0327 备注零基础学Python 订阅专…

浏览器渲染原理-解释回流重绘以及为什么transform效率高

浏览器是如何渲染页面 当浏览器的网络线程收到 HTML 文档后&#xff0c;会产生一个渲染任务&#xff0c;并将其传递给渲染主线程的消息队列。在事件循环机制的作用下&#xff0c;渲染主线程取出消息队列中的渲染任务&#xff0c;开启染流程。 整个渲染流程分为多个阶段&#xf…

HW面试经验分享 | 某服蓝队初级

前言 依稀记得是22年 7、8月份参加的HW&#xff0c;当时是比较炎热的时候&#xff0c;但又夹杂一丝秋意。也是头一次去离家乡比较远的地方&#xff0c;多少有点忐忑……&#xff08;怕被噶腰子、水土不服、吃穿用住没着落等等&#xff09;&#xff0c;但最终也是平安无事且顺利…

笔记84:关于递归法的一些感悟

题目1&#xff1a;二叉树的前序遍历 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(…

独立服务器如何安装Webmin面板

本周有一个客户&#xff0c;购买Hostease的独立服务器&#xff0c;询问我们的在线客服&#xff0c;独立服务器支持安装Webmin及如何安装的问题。我们为用户提供教程&#xff0c;用户很快完成了设置。在此&#xff0c;我们分享这个操作教程&#xff0c;希望可以对您有帮助。 Web…

SkyWalking 为所有的API接口增加 tag

背景胡扯 线上接口报错&#xff0c;接着被 SkyWalking 抓到&#xff0c;然后 SkyWalking 触发告警&#xff0c;最后老板你&#xff0c;让你辛苦一下&#xff0c;在明早上班前把这个bug 改了&#xff0c;并告诉你你是全公司的希望。谁说不是呢&#xff1f;为公司业务保驾护航&a…

成都欣丰洪泰文化传媒有限公司领航电商新纪元

在当今数字化飞速发展的时代&#xff0c;电商行业异军突起&#xff0c;成为推动经济增长的重要力量。在这股浪潮中&#xff0c;成都欣丰洪泰文化传媒有限公司以其专业的电商服务脱颖而出&#xff0c;成为业界的佼佼者。本文将带您一探这家公司的独特魅力和专业服务&#xff0c;…