C++的set / multiset容器

一、介绍

        C++的set容器又被称为集合,所有元素在被插入后都会自动排序

二、数据结构

        set / multiset属于关联式容器,底层数据结构是用二叉树实现的。

        其余的容器比如vectordequelist等为序列式容器,因为他们底层使用线性序列结构,里面存储的是元素本身。而关联式容器所存储的是键值对<key,value>,这种方式相比于线性序列数据检索效率更高

        常见的关联式容器有:map、set、multimap、multiset,这四种容器的共同点是:

使用红黑树 / 平衡搜索树作为其底层结构,容器中的元素是一个有序的序列

三、set与multiset的区别

3.1 二者插入元素的区别

set不允许值相同的元素出现
multiset允许相同的元素出现

         在插入元素的时候,set的insert函数会返回bool类型的值,来表示插入是否成功。

注意  其实set的insert返回值是一个pair类型的变量,其中包含着bool,也有别的值)

而multiset的insert函数不会返回任何值。

3.2 pair “对组”类型

        pair类型,称为对组类型。它是由两种类型的数据组合的,数据的类型是模板(由自己确定)

对组的初始化有以下两种形式:

pair<type1, type2> p(value1, value2);

创造一个第一个变量类型为type1,第一个值为value1

第二个变量类型为type2,第二个值为value2的对组对象

pair<type1, type2> p = make_pair(value1, value2);同上

         对组的第一个变量为first,第二个变量为second。为了便于理解,请看以下例子:

//打印Pair变量的信息
void printPair(const pair<string, int>& p) {
	cout << p.first << "今年" << p.second << "岁." << endl;
}

int main() {
    pair<string, int> c("小C", 18);
    printPair(c);
    pair<string, int> daniel = make_pair("Daniel", 30);
    printPair(daniel);

    return 0;
}

         程序执行的结果如下所示:

四、set / multiset的使用

        为了便于观察,我们以下所有测试用例,均采用自己编写的printSet函数打印全部元素:

//打印set集合的所有元素
void printSet(const set<int> &s) {
	if (s.empty()) {
		cout << "This set is EMPTY now." << endl;

		return;
	}

	for (int elem : s) {
		cout << elem << ' ';
	}
	cout << endl;
}

4.1 set初始化

声明解释
set<T> s初始化一个空集合
set<T> s(const set<T> &s1)

拷贝构造函数,用s1的所有元素初始化一个集合

set<int> s;//初始化空集合
printSet(s);
set<int> s1(s);//拷贝构造函数
printSet(s);

         程序执行结果如下所示:

4.2 set赋值

        两个set对象之间的赋值采用重载后的=运算符

set<int> s0;//s0 = EMPTY
set<int> s1;//s1 = EMPTY

for (int i = 0; i < 10; i++) {
	s0.insert(i);
}//s0 = 0 1 2 3 4 5 6 7 8 9

cout << "Before s1 = s0, s1 : ";
printSet(s1);//s1 = EMPTY
s1 = s0;
cout << "Before s1 = s0, s1 : ";
printSet(s1);//s1 = 0 1 2 3 4 5 6 7 8 9

         程序执行结果如下所示:

4.3 set容量和大小

声明解释
empty()判断集合是否为空
size()

返回集合中元素的个数

set<int> s0;//s0 = EMPTY
set<int> s1;//s1 = EMPTY

for (int i = 0; i < 10; i++) {
	s0.insert(i);
}//s0 = 0 1 2 3 4 5 6 7 8 9

cout << "s0.empty() : " << boolalpha << s0.empty() << endl;//false
cout << "s0.size() : " << s0.size() << endl;//10

         程序的执行结果如下所示:

4.4 set交换元素

声明解释
swap(set<T> s1)将当前集合的全部元素和s1的全部元素交换
set<int> s0;
set<int> s1;

for (int i = 0; i < 10; i++) {
	s0.insert(i);
	s1.insert(i+10);
}

cout << "Before Swap : " << endl;
cout << "s0 : "; printSet(s0);
cout << "s1 : "; printSet(s1);

s0.swap(s1);//swap

cout << "After Swap : " << endl;
cout << "s0 : "; printSet(s0);
cout << "s1 : "; printSet(s1);

        程序如下所示:

4.5 set插入元素

声明解释
insert(T elem)将elem插入当前集合
set<int> s0;
cout << "Before Insert : " << endl;
cout << "s0 : "; printSet(s0);

//为s0 插入0~9
for (int i = 0; i < 10; i++) {
	s0.insert(i);
}

cout << "Atfer Insert : " << endl;
cout << "s0 : "; printSet(s0);

         程序运行的结果如下所示:

4.6 set删除元素

声明解释
erase(T elem)删除当前集合中值为elem的元素
erase(pos)

删除当前集合中pos迭代器指向的元素

erase(begin, end)删除当前集合中[begin, end)区间的元素
clear()删除当前集合中全部元素
set<int> s0;
for (int i = 0; i < 10; i++) {
	s0.insert(i);
}//s0 = 0 1 2 3 4 5 6 7 8 9 

//删除值为7的元素
s0.erase(7);
cout << "s0 : "; printSet(s0);//s0 = 0 1 2 3 4 5 6 8 9

//删除begin+1指向的元素
set<int>::iterator s_begin = s0.begin();
s_begin++;
s0.erase(s_begin);
cout << "s0 : "; printSet(s0);//s0 = 0 2 3 4 5 6 8 9

//删除[begin+2, end-2)
s_begin = s0.begin();
set<int>::iterator s_end = s0.end();
s_begin++; s_begin++;//begin + 2
s_end--; s_end--;//end - 2
s0.erase(s_begin, s_end);
cout << "s0 : "; printSet(s0);//s0 = 0 2 8 9

//清空全部元素
s0.clear();
cout << "s0 : "; printSet(s0);//s0 = EMPTY

         程序运行结果如下所示:

4.7 set查找与统计指定元素

声明解释
find(T elem)

找到指向第一个elem元素的迭代器

(如果没有找到,则返回end())

count(T elem)

统计值为elem的元素个数

multiset<int> ms0;
for (int i = 0; i < 10; i++) {
	ms0.insert(i); 
	ms0.insert(i);
}//s0 = 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 

cout << "s0 : "; printSet(ms0);

multiset<int>::iterator it = ms0.find(7);
cout << *it << endl;//7
cout << "ms0.find(5) : " << ms0.count(5) << endl;//2

         程序的执行结果如下所示:

4.8 set排序

4.8.1 内置类型的set排序

        set容器默认的排序规则:从小到大。可以使用仿函数改变排序规则。

        仿函数可能是一个类或者结构体,重载了函数调用运算符operator(),使得对象可以函数一样被调用。它本质上就是一个只重载了operator( ) 的类,它的出现是为了代替C语言中的函数指针。下面用一个例子具体表现仿函数set内置排序中的应用:

//打印set集合的所有元素
template<typename T,typename ELEM_T>
void printSet(const set<ELEM_T, T>& s) {
	if (s.empty()) {
		cout << "This set is EMPTY now." << endl;

		return;
	}

	for (ELEM_T elem : s) {
		cout << elem << ' ';
	}
	cout << endl;
}

//仿函数:重载了()运算符的类
class MySetCompare {
public :
	//重载()运算符
	bool operator()(int a, int b) const{
		return a > b;//降序
	}
};

int main() {
	set<int, MySetCompare> ms0;
	for (int i = 0; i < 10; i++) {
		ms0.insert(i);
	}//s0 = 9 8 7 6 5 4 3 2 1 0  
	cout << "s0 : "; printSet(ms0);

	return 0;
}

  4.8.2 自定义数据类型set排序

        和上边内置类型类似,我们只需要通过仿函数设置自己的排序规则即可。以下例子为了便于说明问题,将成员均设置为public

//士兵类:排名规则,先按年龄升序,再按身高降序
class Soldier {
public:
	string name;//姓名
	int age;//年龄
	int height;//身高

	Soldier(string n, int a, int h) : name(n), age(a), height(h) {}
};

//重载<<运算符
ostream& operator<< (ostream& cout, Soldier &s) {
	cout << s.name << "今年" << s.age << "岁,身高" << s.height << "cm。";

	return cout;
}

//仿函数:比较规则
class SoldierCompare {
public:
	bool operator()(Soldier s0, Soldier s1) const {
		if (s0.age == s1.age) {
			return s0.height > s1.height;
		}
		else {
			return s0.age < s1.age;
		}
	}
};

//打印set集合的所有元素
void printSet(const set<Soldier, SoldierCompare>& s) {
	if (s.empty()) {
		cout << "This set is EMPTY now." << endl;

		return;
	}

	for (Soldier elem : s) {
		cout << elem << endl;
	}

	cout << endl;
}

int main() {
	set<Soldier, SoldierCompare> soldiers;

	//添加士兵
	Soldier A("A", 30, 180);
	Soldier B("B", 18, 189);
	Soldier C("C", 35, 165);
	Soldier D("D", 18, 175);
	Soldier E("E", 35, 170);
	Soldier F("F", 25, 180);
	soldiers.insert(A);
	soldiers.insert(B);
	soldiers.insert(C);
	soldiers.insert(D);
	soldiers.insert(E);
	soldiers.insert(F);

	cout << "soldiers : " << endl;
	printSet(soldiers);
	
	return 0;
}

        上述例子中的程序运行结果如下所示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值