C++关联容器操作详解

文章目录

  • 1.关联容器
  • 2.使用关联容器
    • 使用map
    • 使用set
  • 3.关联容器概述
    • 定义关联容器
      • 初始化map与set
      • 初始化multimap与multiset
      • 示例
    • pair类型
  • 4.关联容器操作
    • 关联容器的迭代器
    • 添加元素
      • inset
      • insert的返回值
      • multimap和multiset的insert
      • 示例
    • erase
    • 下标操作
    • 访问元素
      • find代替下标操作
      • multimap和multiset的查找
      • 使用面向迭代器版本的查找方式
      • equal_range
      • 示例
  • 5.无序容器

1.关联容器

按关键字有序保存元素:

  • map:关联数组,保存关键字-值对
  • set:关键字即值,即只保存关键字的容器
  • multimap:关键字可重复出现的map
  • multiset:关键字可重复出现的set

无序集合:

  • unordered_map:哈希函数组织的map
  • unordered_set:哈希函数组织的set
  • unordered_multimap:哈希函数组织的map,关键字可以重复出现
  • unordered_multiset:哈希函数组织的set,关键字可以重复出现

2.使用关联容器

使用map

map是关键字-值对的集合。被称为无序数组,即通过关键字即可查找到对应的值。

通过map来统计单词在输入中出现的次数:

  • 定义map:必须指定关键字和值的类型,分别是string类型和size_t类型
  • 以单词名字作为关键字下标,相当于数组。
  • 在map中的元素以pair类型存储,它具有first和second成员,分别表示关键字和值。
	map<string, size_t> word_count;
	string word;
	while (cin >> word)
	{
		++word_count[word];
	}
	for (const auto& x : word_count)
	{
		cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl;
	}

使用set

set存储关键字,是关键字的集合

使用set来忽略常见的单词:

  • 定义set:指定元素类型,string类型
  • find返回迭代器,没有找到则指向尾后迭代器。
	map<string, size_t> word_count;
	set<string> exclude{ "the","woaini","666","hhh","adaasa","dawdadadas","ylh","dwadada"};

	string word;
	while (cin >> word)
	{
		//找到返回word,没找到返回尾后迭代器
		if (exclude.find(word) == exclude.end())
		{
			++word_count[word];
		}
	}
	for (const auto& x : word_count)
	{
		cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl;
	}

3.关联容器概述

定义关联容器

初始化map与set

  • 使用一个关键字类型和值类型来定义map,只需一个关键字来定义set。

  • 默认构造函数:创建一个空容器。

  • 初始化同类型的拷贝。

  • 使用值范围来初始化关联容器。

  • 也可以进行值初始化,列表初始化。

    	map<string, string> a{
    		{"ylh","da"},
    		{"dad","oid"},
    		{"oda","cece"} };
    	set<string> b{ "wad","Da","grg","plda" };
    

初始化multimap与multiset

允许有重复关键字:

map和set如果有多个关键字,则会跳过;multimap和multiset会继续存储

·vector<int> a;
	for (size_t i = 0; i != 10; ++i)
	{
		a.push_back(i);
		a.push_back(i);
	}

	set<int> b1{ a.cbegin(),a.cend() };			//set.size()= 10
	multiset<int> b2{ a.cbegin(),a.cend() };	//multiset.size()= 20

示例

map存储姓氏和家庭成员的名字: 支持添加新成员插入

	map<string,vector<string>> family;
	string first_name,child_name;
	while ( [&]()->bool {cout<<"请输入家庭的姓:"; return cin>>first_name && (first_name != "end");}() )
	{
			cout<<"请输入孩子的名字:";
			while (cin>>child_name && (child_name != "end"))
			{
				family[first_name].push_back(child_name);
			}	
	}
	for (auto it = peo.cbegin(); it != peo.cend(); ++it)
	{
		cout << (*it).first << ": ";
		for_each((*it).second.begin(), (*it).second.end(), [](const string& str) {cout << str << "  "; });
		cout << endl;
	}

pair类型

include < utility > 头文件

一个pair保存两个数据成员。

pair用来生成特定类型的模板,创建一个pair:提供两个类型名。pair的数据成员将具有对应的类型。

pair<string, string> a;
pair<string, size_t> b;
pair<string, list<size_t>> c;

当然也可以进行值初始化。

pair的成员被命名为first和second,是共有的。普通的成员访问符号可以直接访问。

pair类型的函数返回值:

pair<string, int> process(vector<string>& v)
{
	if (!v.empty())
	{
		return { v.back(),v.back().size() };	//显式列表初始化
	}
	else
	{
		return pair<string, int>();				//隐式构造返回值
	}
}

pair被其他容器包含的情况:

vector的元素为pair,pair再隐式或显式构造对象

vector<pair<string, int>> a;
string temp;
int num;
while (cin >> temp >> num)
{
	a.push_back({ temp,num });	//隐式构造
}

补充:利用vector构造pair元素对象的几种方式:

	a.push_back({ temp,num });			//隐式构造
	a.push_back(pair<string, int>{temp, num}); //显式构造
	a.push_back(make_pair(temp, num));	//make_pair构造
	a.emplace_back(temp, num);			//emplace_back构造函数

示例:在用map存储姓氏和名字的基础上,使用pair存储名字和生日

  • 使用lamdba表达式更快捷

  • lamdba表达式采用&捕获引用,指明返回类型**,最后有一个小括号表示要传递的形参数值,此题中不用传递形参,所以此处为空。**

  • 打印的方式:可以采用二维数组的方式打印:

    首先由map得到 string和 vector容器,打印string;在由vector容器得到pair类型,再有pair的first和second得到结果。

	map<string, vector<pair<string,string>>> peo;	//姓: 名字和生日
	string fname, name, birth;
	int choice = 0;
	while ([&]()->bool {cout << "请输入家庭姓氏: "; return (cin >> fname) && (fname != "quit"); }())
	{
		while ([&]()->bool {cout << "请输入姓名: "; return (cin >> name) && (name != "quit"); }())
		{
			if ([&]()->bool {cout << "请输入生日: "; return (cin >> birth) && (birth != "quit"); }())
			{
				peo[fname].emplace_back(name, birth);
			}
		}
	}

	cout << endl;
	for (const auto& i : peo)
	{
		cout << i.first << ": " << endl;
		for (const auto& y : i.second)
		{
			cout << y.first << ", " << y.second << endl;
		}
	}

4.关联容器操作

set和map关键字和值的类型:

  • key_type:容器类型的关键字类型
  • mapped_type:适用于map,表示值类型。
  • value_type:对于set:关键字类型。对于map:表示pair

关联容器的迭代器

迭代器map_it指向map的第一个元素,解引用迭代器得到一个pair的引用,pair的first为const,second可以修改。

map<string, int> a{ {"ylh",666} };
auto map_it = a.begin();		//map的迭代器
cout << map_it->first << " " << map_it->second << endl;
map_it->first = "woaini";	//错误,关键字是const
++map_it->second;	//可以递增迭代器改变元素

set: set关键字只读,无法修改。

set<int> a{ 1 };
auto set_it = a.begin();
++(* set_it)	//错误,set关键字只读

map和set的遍历

for (auto it=a.cbegin();a!=a.cend();++a)
{
	cout<<(*it).first<<" "<<it->second<<endl;
}

注意:使用set,map,multiset,multimap遍历时,迭代器按关键字升序遍历元素

ps:通常不对关联容器使用泛型算法。

添加元素

inset

set中使用insert:

vector<int> a{ 2,4,6,8,10 };
set<int> set2;
set2.insert(a.cbegin(), a.cend());	//5个
set2.insert({ 1,3,5,7,1,3,5,7 });	//4个
//总共9个

map中使用insert:(和创建pair类型的对象一样)

map<string, int> a;
a.insert(pair<string, int>{"ylh", 666});				//显式构造pair
a.insert({ "word",111 });								//初始化列表
a.insert(make_pair("word", 222));						//make_pair构造
a.insert(map<string, int>::value_type{ "word",333 });	//构造一个pair,并且构造一个对象

insert的返回值

map和set的insert或者emplace返回一个 pair类型

pair的first成员是一个迭代器,指向已经具有的关键字的元素

  • first指向一个map的iterator,可以再次解引用迭代器得到pair,并使用first得到关键字second得到值

pair的second成员是一个bool值,指出插入成功,还是已经存在于容器。

  • 如果关键字已经存在,则second成员返回false,插入失败。
  • 如果关键字不存,则second成员返回true,插入成功。
map<string,int> a;
a.insert({ "word",111 });
auto it=a.insert(make_pair("word", 222));
//it的first指向已经具有的关键字的元素。  second表示bool。
cout << (it.first)->first << " " << (it.first)->second << " " << it.second;
//分别打印:     word      111    0                                 

insert返回值的完整类型:pair包含一个map的迭代器与bool

pair<map<string,int>::iterator,bool> it=a.insert(make_pair(word,1));

multimap和multiset的insert

允许一个关键字有多个对应的值。

multimap<string, int> a;
a.insert(pair<string, int>{"ylh", 666});
a.insert({ "word",111 });
a.insert(make_pair("word", 222));
a.insert(map<string, int>::value_type{ "word",333 });

示例

单词计数,利用insert的返回值来递增关键字的值的计数。

	++word_count.insert({ word,0 }).first->second;
  /*auto it = word_count.insert(make_pair(word, 1));
	if (!it.second)
	{
		++it.first->second;
	}*/

一个程序:思考这个程序打印能否使用for_each或者copy算法???

vector<size_t> temp{ 1,2,3,4,5,6 };
map<string, vector<size_t>> a;
string word = "word";
pair<map<string, vector<size_t>>::iterator, bool> it = a.insert(make_pair(word,temp));

for (auto it = a.cbegin(); it != a.cend(); ++it)
{
	cout << it->first << " ";	//得到string
	for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2)
	{
		cout << *it2 << " ";
	}
	cout << endl;
}
//for_each(a.cbegin()->first, a.cbegin()->first, [](const string& str) {cout << str << " :"; });
//for_each(a.cbegin()->second.cbegin(), a.cbegin()->second.cend(), [](const size_t& num) {cout << num << " "; });

erase

删除元素,接受一个key_type,即关键字,删除关键字匹配的所有元素。

对于map和set:由于他们必须是不重复的关键字,所以erase的结果要么为0,要么为1。0表示删除的元素不存在

对于multimap和multiset:他们接受重复关键字,所以erase的结果可以是大于1,表示删除的个数。

	multimap<string, int> a{ {"wo",1},{"ar",2},{"wo",2} };
	cout<<a.erase("wo");	//结果为2

同时erase也接受迭代器的指定的范围删除。

下标操作

下标运算符和at函数。

set类型不支持下标;multimap和unordered_multimap不能进行下标操作(可以会有多个值对应关键字)。

对map执行下标操作: 如果关键字在map中,会找到对应的值;如果不在map中,会创建一个元素,默认值为0

map<string, int> a;
cout << a["wda"] << endl;	//使用默认的字-值对:wda - 0

at访问,如果关键字不存在,则会报错。

对map进行下标操作,会获得一个mapped_type;对map进行解引用,会获得一个value_type。

map的下标操作返回一个左值,既可以读也可以写。

访问元素

find和count:find查找元素出现的位置,没找到则返回尾后迭代器;count返回元素出现的次数。

find代替下标操作

下标操作如果关键字不在map中,则会创建一个默认的字-值对;但我们不希望改变map的结构

find没有找到会返回 end。

map<string, int> a{ {"wo",1},{"ao",2},{"po",3} };
if (a.find("ylh") == a.end())
{
	cout << "没有找到!\n";
}

multimap和multiset的查找

multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555} };
auto a_count = a.count("ylh");	//查找出指定字出现的次数
auto it_beg = a.find("ylh");	//查找第一个出现的位置
while (a_count)
{
	cout << it_beg->second << " ";	//打印目标
	++it_beg;						//递增到下一目标	
	--a_count;						//计数递减
}

使用面向迭代器版本的查找方式

lower_bound:返回的迭代器指向第一个具有给定关键字的元素。

upper_bound:返回的迭代器指向最后一个匹配元素之后的位置

multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555},{"zdwa",555},{"zoef",565} };
// beg指向第一个符合条件的元素,end指向最后一个符合条件的元素的下一个位置
for (auto beg = a.lower_bound("ylh"), end = a.upper_bound("ylh"); beg != end; ++beg)
{
	cout << beg->second << endl;
}

ps:他们的返回值可以作为一个迭代器范围。

如果没有元素与给定关键字匹配,则lower_bound和upper_bound返回相等位置的迭代器。

equal_range

接受一个关键字,返回一个迭代器pair

beg为a第一个出现关键字的位置的一个迭代器pair:

pair的first成员为找到的第一个位置的迭代器,second成员为找到的最后一个符合关键字的下一个位置迭代器。

pair<multimap<string,int>::iterator,multimap<string,int>::iterator>

其实就是lower_bound和upper_bound的简写版:

multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555},{"zdwa",555},{"zoef",565} };
for (auto beg=a.equal_range("ylh");beg.first!=beg.second;++beg.first)
{
	cout << beg.first->second << endl;
}

beg的first为找到的第一个符合条件的迭代器,他是一个pair类型对象解引用或者->运算符得到pair的first或者second。

示例

在multimap中查找一个string值元素,并删除它:

multimap<string, string> a{ {"ylh","iot"} ,{"wad","dwapd"},{"ylh","posa"},{"ylh","zbcd"},{"xsd","podv"}};
auto fin_it = a.find("ylh");	//查找关键字出现的位置
auto count = a.count("ylh");					//统计关键字出现的次数
while (count)
{
	if (fin_it->second == "zbcd")		//如果找到了值为zbcd的元素位置
	{
		a.erase(fin_it);			//删除元素
		break;
	}
	++fin_it;
	--count;
}

for (auto beg = a.cbegin(); beg != a.cend(); ++beg)
{
	cout << beg->first << ": " << beg->second << endl;
}

删除指定的所有关键字和值

	multimap<string, string> a{ {"ylh","iot"} ,{"wad","dwapd"},{"ylh","posa"},{"ylh","zbcd"},{"xsd","podv"}};
	auto beg = a.equal_range("ylh"); 	//找到关键字的位置
	a.erase(beg.first,beg.second);		//删除全部此关键字

5.无序容器

无序容器不使用比较运算符来组织元素,而是使用一个哈希函数,和关键字的==运算符。

使用无序容器来统计单词,但是不会以字典序的形式输出

unordered_map<string, size_t> word_count;
	string word;
	while (cin >> word)
	{
		++word_count.insert({ word,0 }).first->second;
	}
	for (const auto& x : word_count)
	{
		cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl;
	}

无序容器的桶操作

  • 无序版本优势:当容器中key没有明显的顺序关系时更有用,且不需要耗费多余的时间来维护容器中的key序列
  • 有序版本优势:当容器中key有明显的顺序关系时更有用,且我们不需要考虑排序问题,容器自动维护序列(字典序)
  • map的key_value是有序的,set本身就是有序的,有序容器的操作可以用于无序容器
  • 无序容器访问元素时,首先计算元素的哈希值,它指出应该搜索哪个桶,因为容器将哈希值相同的元素放在一个桶中

你可能感兴趣的:(C++,c++,算法,数据结构,哈希算法,c语言)