C++学习笔记——关联式容器(上)

在说《二叉搜索树》时,就说结局会浮现出来,其实前面的数据结构,都是为这篇博文的。

》关联式容器

在前面说到vector、list、deque等序列式容器,其数据的存储都是线性的,其查询的时候时间复杂度都比较高。关联式容器也是存储的数据,但是这里的数据都是以键值对的形式存在的,其检索的效率是非常高的。关联式容器有两种不同的结构,接下来要说的是树型结构的关联式 。

该结构的容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用红黑树作为其底层结果,容器中的元素是一个有序的序列。

》键值对

用来表示两种数据一一对应的数据结构,就像一把钥匙对应一把锁,该结构由两个成员构成,key和value。key对应的就是value,key就是钥匙,value就是锁。

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)
     {}
};

》map

1、概念

 map关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。在内部,map中的元素总是按照键值key进行比较排序的。因此键值key用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。
map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。其他树形结构都没有重载[]。

map中key是独一无二的,不能重复,而value是可以重复的。就像一把钥匙只能开一把锁,但是一把锁可以被多把钥匙打开。

2、使用

map的详细介绍和使用,这里有点我下面只是简单的使用一下。

#include<map>
void MapTest()
{
	map<int, string> mp1;
	mp1.insert(make_pair(2, "大学"));
	mp1.insert(make_pair(4, "中国"));
	mp1.insert(make_pair(3, "陕西"));
	for (const auto& e : mp1)
	{
		cout << e.first << "--->" << e.second << endl;
	}
    //对于[]的操作,如果这个key存在则会修改原本的value
    //不存在,则会插入这个key和其value。
	mp1[5] = "中学";
	mp1[4] = "China";
	cout << "===================" << endl;
	for (const auto& e : mp1)
	{
		cout << e.first << "--->" << e.second << endl;
	}
}

》multimap

1、概念

multimap其实就是map的进化版本,它的key是可以重复的。仅此而已,它的头文件都是<map>。由于key值可以相同,于是multimap没有重载[],因为[]访问会有二义性。

2、使用

#include<map>
void MultimapTest()
{
	multimap<int, string> mulp;
	mulp.insert(make_pair(4, "四"));
	mulp.insert(make_pair(4, "4"));
	mulp.insert(make_pair(4, "four"));
	for (const auto& e : mulp)
	{
		cout << e.first << "--->" << e.second << endl;
	}

}

》set

1、概念

set的value就是它的key,即set的键值对就是<value, value>,但是set的键还是不能重复,由于value和key是相同的,所以在进行操作时,不用构建键值对,正是由于value和key相同,于是又达成数据去重的成就。set没有重载[]。

2、使用

void SetTest()
{
	int arr[] = { 1,5,4,9,6,3,4,7,1,8,5 };
	set<int> sarr;
	for (int i = 0; i < sizeof(arr) / 4; ++i)
	{
		sarr.insert(arr[i]);
	}
	for (const auto& e : sarr)
	{
		cout << e << endl;
	}
}

》multiset

1、概念

multiset是set的进阶,也就是说multiset的key也是可以重复的。

2、使用

void MultisetTest()
{
	int arr[] = { 1,5,4,9,6,3,4,7,1,8,5 };
	multiset<int> sarr;
	for (int i = 0; i < sizeof(arr) / 4; ++i)
	{
		sarr.insert(arr[i]);
	}
	for (const auto& e : sarr)
	{
		cout << e << endl;
	}
}

与set进行对比。

》OJ实践

leetcode——692. 前K个高频单词

给一非空的单词列表,返回前 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int> mp1;
        multimap<int, string, greater<int>> mp2;//降序排序
        vector<string> ret;
        for(const auto& str : words)//先用map进行数量上的排序
            ++mp1[str];
        for(const auto& m : mp1)
            mp2.insert(make_pair(m.second, m.first));//在用multimap输出,保证重复次数的单词也会统计
        auto b = mp2.begin();
        while(b != mp2.end() && k--)
        {
            ret.push_back(b->second);
            ++b;
        }        
        return ret;
    }
};

》又见红黑树

1、红黑树的改造

前面所说的红黑树,只是为了实验红黑树的性质,以及验证代码实现的结果。上面的四种容器都是以红黑树为底层结构,所以一棵红黑树却可以实现四种容器,这就需要对前面的红黑树进行改造。

由于set和map不一样,set只有key,map有key和val所以,需要用到仿函数。

前面的herder,就是用到红黑树的迭代器部分的end()。

改造的的源代码,请点击红黑树的改造

主要要说的是我们的红黑树迭代器。

2、红黑树的迭代器

在前面说红黑树在创建的时候增加了一个headers的头节点,所以这次来看看这个结点的作用。

迭代器的简单来说,就是指针。于是我们实现的功能大抵有++,--,->,*等。这里主要阐述一下++。

红黑树是一棵二叉搜索树,所以对于其的中序遍历是一个有序的数组,其实它的++遍历就是一个中序遍历。所以实现++就是把中序遍历分解成一步一步的操作。

红黑树的迭代器的begin是headers的left_,就是红黑树最左边的结点,即最小的结点。中序遍历便是从这里开始的。

现在开始分解步骤,假设cur是当前要++的结点,类比中序遍历,分为下面的三种情况:

cur是左孩子:下一次访问的应该是它的父结点,因为右孩子 > 父结点 > 左孩子

cur是父结点:下一次访问的是右孩子的左子树,如果左子树不存在,就是cur的右孩子

cur是右孩子:由于父结点已经被访问了,则需要向上回溯。

综上,可以归结为++后有没有右孩子。

如果存在右孩子:就直接找右孩子的最左边的结点,如果不存在左子树,++后就是该结点的右孩子。

如果不存在右孩子:就要向上回溯了,找到一个父亲结点,这个父亲结点不是祖父结点的右孩子就行,因为如果是右孩子,这个父结点又是被访问过的。就继续向上回溯。

template <class T>
struct RBTreeIterator
{
	typedef RBTreeNode<T>* pNode;
	typedef RBTreeIterator<T> Self;
	pNode node_;

	RBTreeIterator(pNode node)
		:node_(node)
	{}

	T& operator*()
	{
		return node_->data_;
	}

	T* operator->()
	{
		return &operator*();
	}

	bool operator!=(Self rbt)
	{
		return node_ != rbt.node_;
	}
	Self operator++()
	{
		if (node_->pRight_)
		{
			node_ = node_->pRight_;
			while (node_->pLeft_)
			{
				node_ = node_->pLeft_;
			}
		}
		else
		{
			pNode parent = node_->pParent_;
			while (node_ == parent->pRight_)
			{
				node_ = parent;
				parent = node_->pParent_;
			}
			node_ = parent;
		}
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(*this);
		++(*this);
		return tmp;
	}
};

彩蛋

既然是两种结构的容器,那么另一种……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值