【C++】STL——用一颗红黑树封装出map和set

news2024/10/6 16:28:16

用一颗红黑树封装出map和set

在这里插入图片描述

文章目录

  • 用一颗红黑树封装出map和set
    • 一、前言
    • 二、红黑树模板参数的控制
    • 三、模板参数中仿函数的增加
    • 四、红黑树正向迭代器的实现
    • 五、红黑树的反向迭代器的实现
    • 六、红黑树的begin()和end()
    • 七、红黑树的rbegin()和rend()
    • 八、[ ]下标访问运算符重载
    • 九、红黑树的Find查找函数
    • 十、红黑树(修改版)源码链接
    • 十一、set、map模拟实现代码
      • 1.set的代码
      • 2.map的代码

一、前言

我们都知道set是K模型的容器,而map是KV模型的容器,但是它俩的底层都是用红黑树实现的,上篇博文中我们模拟实现了一颗红黑树,接下来将对其进行改造,继而用一颗红黑树封装出map和set。本质上map和set其内部的主要功能都是套用了红黑树现成的接口,只是稍作改动即可


二、红黑树模板参数的控制

既然set是K模型,map是KV模型,正如stl库里的map和set,如图所示:

image-20230404201210529

我们发现map和set都是复用的同一颗红黑树,并且实现的都是Key_value模型。

优势:两个容器都可以复用同一颗红黑树,体现泛型编程的好处。

image-20230404202750174

通过这里就能够很清晰的看出库里的节点的存储类型是根据set和map的第二个模板参数决定的,第二个参数存的是什么,节点存的就是什么类型,继而可以满足set和map的需求,而现在又可能引发一个新的问题:

  • 既然数据类型看第二个模板参数,那第一个模板参数有何用处?

因为在红黑树中,无可避免会要求实现find等对K有需求的函数,因为find函数主要是通过Key进行查找的,如若省略第一个模板参数,那么map就无法进行find查找操作

image-20230404203151947

接下来,我们按照库里红黑树的样子对我们自己写的进行一个调整:

//节点类
template <class T>
struct RBTreeNode
{
	//三叉链结构
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
    //存储的数据
	T _data;
	//节点的颜色
	Colour _col;
	//构造函数
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(Red)
	{}
};

对红黑树的模板参数修改好了,那么我map(KV模型)和set(K模型)自然而然就能够适配了:

  • set:
template<class K>
class set
{
public:
	//...
private:
	RBTree<K, K> _t;
};
  • map:
template<class K, class V>
class map
{
public:
	//...
private:
	RBTree<K, pair<K, V>> _t;
};

三、模板参数中仿函数的增加

由于现在红黑树的节点类型是T,当容器为set时,T就是键值Key,可以直接进行比较,当容器是map时,T就是pair<Key, Value>,此时不能直接比较,而需要从此键值对中取出Key,再拿Key进行大小比较。但是库里面pair比较大小的方式并不是我们想要的,并且map和set想要比较的数据是不同的。有的同学可能会说,这里我们再写一个比较函数,这是不行的,会和库里面函数冲突。

image-20230404204449956

为了解决这一点,我们可以在红黑树的模板参数上再加一层仿函数,此参数专门用于获得Key类型,而这个仿函数的实现是在map和set内部封装的,通过仿函数,直接从红黑树的节点中取出想要的Key。

注意:map和set需要比较的数据为Key值,但是map和set的结构不同,所以需要在map和set类中实现不同的仿函数。

仿函数,就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。

具体操作如下:

  • map:
template<class K, class V>
class map
{
  struct MapKeyOfT
 {
	const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
 };
private:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
  };
}
  • set:
template<class K>
class set
{
	struct SetKeyOfT
	{
	  const K& operator()(const K& key)
		{
			return key;
		}
};
private:
	RBTree<K, K, SetKeyOfT> _t;
  };
}

下面画图演示具体的调用情况:

image-20230405130013257


四、红黑树正向迭代器的实现

红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。

//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node; //结点的类型
	typedef __TreeIterator<T, Ref, Ptr> Self; //正向迭代器的类型

	Node* _node; //正向迭代器所封装结点的指针
};

而在其内部,我们要完成如下操作:

  • 1、构造函数
  • 2、*和->运算符重载
  • 3、!=和==运算符重载
  • 4、++运算符重载
  • 5、–运算符重载

接下来具体展开演示:

  • 1、构造函数

构造函数我们直接通过一个节点的指针从而构造一个正向迭代器即可。

//构造函数
__TreeIterator(Node* node)
	:_node(node) //根据所给结点指针构造一个正向迭代器
{}
  • 2、*和->运算符重载

*运算符就是解引用,直接返回对应节点数据的引用即可;而->运算符返回的是对应节点数据的指针。

Ref operator*()
{
	return _node->_data; //返回结点数据的引用
}

Ptr operator->()
{
	return &_node->_data; //返回结点数据的指针
}

注意:这里operator->和list中实现是一样的,返回的是地址的原因是连续访问,编译器会把连续的operator->->优化成operator->.

  • 3、!=和==运算符重载

!=运算符直接返回两个节点是否不同,而==运算符直接返回两个节点是否相同即可。

//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{
	return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{
	return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
  • 4、++运算符重载

++运算符又分前置++和后置++

image-20230404211516458

  • 前置++:

首先,这里红黑树迭代器里的++后的值应该是按此位置开始往后中序遍历的下一个。而这个下一个节点的值理应比原先的大,想要找到这个位置,结合二叉搜索树的性质,理应在右子树当中去寻找,而这又要看右子树是否为空,具体操作如下:

  • 1、右子树非空:直接遍历找到右子树的最左节点即可
  • 2、右子树为空:找祖先里面孩子是父亲左的那个祖先节点,否则继续往上找,直到为空(nullptr)
  • 3、当parent遍历到空时,++结束
  • 4、注意前置++返回的是++后的值
//前置++
Self& operator++()
{
	if (_node->_right) // 右子树不为空
	{
		// 找右子树的最左节点,使得_node指向此节点
		Node* rightmin = _node->_right;
		while (rightmin != nullptr)
		{
			rightmin = rightmin->_left;
		}
		// 最左节点为空
		_node = rightmin;
	}
	else  //_node->_right == nullptr
	{
		// 找祖先中,孩子是父亲左的那个祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		// cur为父亲的左或者父亲为空停止
		while (parent && cur == parent->_right)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}
  • 后置++:

后置++和前置++的唯一区别就在于后置++是返回++前的值,这里只需要在前置++的基础上在一开始把当前节点保存起来,直接调用前置++,最后返回保存的那个节点值即可。

//后置++
Self operator++(int)
{
	Self tmp(*this);
	return tmp;
}

找孩子是祖先的左的那个祖先节点,这样就实现了非递归,这种非递归的前提是必须为三叉链结构

  • 5、–运算符重载

–运算符又分为前置–和后置–,下面分别讨论:

  • 前置–:

–运算符和++运算符相反,–运算符是找比当前位置次小的节点而这个节点,而这又要优先去左子树里寻找,而左子树又分为如下两种情况:

  • 左子树为空:找祖先里面孩子是父亲右的那个节点
  • 左子树非空:找左子树里最右的节点
//前置--
Self& operator--()
{
	if (_node->_left) // 左子树不为空
	{
			// 找左子树的最右节点,使得_node指向此节点
		Node* leftmax = _node->_left;
		while (leftmax != nullptr)
		{
			leftmax = leftmax->_right;
		}

					_node = leftmax;
	}
	else // 左子树为空
	{
		//找祖先中,孩子是父亲右的那个祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (cur && cur == parent->_left)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}

	return *this;
}
  • 后置–:

注意后置–返回的是–前的值,所以先定义tmp把*this保存起来,再套用前置–函数进行自减,最后返回tmp。

//后置--
Self operator--(int)
{
	Self tmp(*this);
	return tmp;
}

五、红黑树的反向迭代器的实现

红黑树的反向迭代器实际上就是正向迭代器的一个封装,因此红黑树的反向迭代器就是一个迭代器适配器

在反向迭代器当中只有一个成员变量,那就是反向迭代器封装的正向迭代器。反向迭代器的中成员函数,都是通过调用正向迭代器对应的函数来完成相应功能的。

//反向迭代器---迭代器适配器
template<class Iterator>
struct ReverseIterator
{
	typedef ReverseIterator<Iterator> Self; //反向迭代器的类型
	typedef typename Iterator::reference Ref; //结点指针的引用
	typedef typename Iterator::pointer Ptr; //结点指针

	Iterator _it; //反向迭代器所封装的正向迭代器

	//构造函数
	ReverseIterator(Iterator it)
		:_it(it) //根据所给正向迭代器构造一个反向迭代器
	{}

	Ref operator*()
	{
		return *_it; //通过调用正向迭代器的operator*返回结点数据的引用
	}
	Ptr operator->()
	{
		return _it.operator->(); //通过调用正向迭代器的operator->返回结点数据的指针
	}

	//前置++
	Self& operator++()
	{
		--_it; //调用正向迭代器的前置--
		return *this;
	}
	//前置--
	Self& operator--()
	{
		++_it; //调用正向迭代器的前置++
		return *this;
	}

	bool operator!=(const Self& s) const
	{
		return _it != s._it; //调用正向迭代器的operator!=
	}
	bool operator==(const Self& s) const
	{
		return _it == s._it; //调用正向迭代器的operator==
	}
};

需要注意的是,反向迭代器只接收了一个模板参数,即正向迭代器的类型,也就是说,反向迭代器不知道结点的引用类型和结点的指针类型,因此我们需要在正向迭代器当中对这两个类型进行typedef,这样反向迭代器才能通过正向迭代器获取结点的引用类型和结点的指针类型。

//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
	typedef Ref reference; //结点指针的引用
	typedef Ptr pointer; //结点指针
};

六、红黑树的begin()和end()

迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在红黑树的public区域进行typedef。

template <class K, class T, class KeyOfT>
class RBTree
{    
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;//普通迭代器
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;//const迭代器
}

其实,STL中的红黑树的底层是有一个哨兵位头结点的,如下所示:

image-20230404211431883

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置。

虽说库里的红黑树实现的是带哨兵位头节点的,但毕竟咱这是模拟(但求大概),综上begin()和end()的指向如下总结:

  • begin():指向红黑树中最小节点(即最左侧节点)的位置
  • end():指向红黑树中最大节点(最右侧节点)的下一个位置,即nullptr
//begin()
iterator begin()//找最左节点
{
	Node* subLeft = _root;
	while (subLeft && subLeft->_left)
	{
		subLeft = subLeft->_left;
	}
	return iterator(subLeft);//调用迭代器的构造函数
}
//end()
iterator end()//返回最右节点的下一个
{
	return iterator(nullptr);
}

当然这里最好再实现一个const版本的begin()和end(),为的是普通迭代器和const迭代器都能够使用,其实主要还是set的迭代器不能被修改,无论是普通迭代器还是const迭代器,其内部都是用const迭代器封装的,因此必须实现一个const版本的begin()和end()。

//begin() 
const_iterator begin() const//找最左节点
{
	Node* subLeft = _root;
	while (subLeft && subLeft->_left)
	{
		subLeft = subLeft->_left;
	}
	return const_iterator(subLeft);//调用迭代器的构造函数
}
//end()
const_iterator end() const//返回最右节点的下一个
{
	return const_iterator(nullptr);
}

如果 map/set 中想使用红黑树中的迭代器,我们需要在 map/set 中进行声明。

声明如下:

如果想取一个类模板中的一个类型,要使用 typedname 进行声明,告诉编译器这是一个类型,并不是一个静态变量

//如果想取一个类模板中的一个类型,要使用 typedname 进行声明。
//告诉编译器这是一个类型,并不是一个静态变量
typedef typename RBTree<K, pair<K, V>, MapKeyOfvalue>::iterator iterator;

注意:typename受限定符限制,尽量放在public下


七、红黑树的rbegin()和rend()

  • rbegin函数返回中序序列当中最后一个结点的反向迭代器,即最右结点。
  • rend函数返回中序序列当中第一个结点前一个位置的反向迭代器,这里直接用空指针构造一个反向迭代器。
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; //结点的类型
public:
	typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器
	
	reverse_iterator rbegin()
	{
		//寻找最右结点
		Node* right = _root;
		while (right&&right->_right)
		{
			right = right->_right;
		}
		//返回最右结点的反向迭代器
		return reverse_iterator(iterator(right));
	}
	reverse_iterator rend()
	{
		//返回由nullptr构造得到的反向迭代器(不严谨)
		return reverse_iterator(iterator(nullptr));
	}
private:
	Node* _root; //红黑树的根结点
};

八、[ ]下标访问运算符重载

我们来看 map 的 [ ] 下标访问操作符,其中 [ ]返回的是mapped_type(pair) 类型。

image-20230410194240906

我们便要对 map 中 insert 的返回值做出修改:

image-20230410194732495

注意,set 中的 insert 也是返回 pair,虽然很反常,但是STL库中确实是这样书写的。

image-20230410194803699

因为只有 set 没有 [ ] 运算符重载,所以我们 set 中不必提供该函数,只用在 map 中提供即可。

首先,我们向 map 中 insert 数据 pair;pair的第一个参数为用户传入的 key 值,第二个参数则是用户声明的第二个模板参数的默认构造函数(如果是 int,则调用 int的构造函数,如果是 string ,则默认构造 string)。

pair<iterator, bool> result = insert(make_pair(key, V()));

然后我们返回迭代器指向的 pair 数据中的second。

//result.first取出迭代器,使用->运算符重载取出data地址,访问second并返回
return result.first->second;

完整函数如下:

V& operator[](const K& key)
{
	pair<iterator, bool> result = insert(make_pair(key, V()));
	//如果存在,则插入失败
	//如果不存在,则插入数据
	//无论是否存在,都返回 second;
	return result.first->second;
}

接下来我们要对红黑树的 Insert 的返回值处进行改动,进而契合 map 的 pair 数据类型。改动有三处,这里贴图大家观察即可。

image-20230410195536234

九、红黑树的Find查找函数

查找的规则很简单,只需要遍历节点即可,具体规则如下:

  1. 如果查询的值 > 当前节点值,遍历到右子树查询
  2. 如果查询的值 < 当前节点值,遍历到左子树查询
  3. 如果查询的值 = 当前节点值,返回当前位置的迭代器
  4. 如果循环结束,说明未查询到,返回End()
//Find查找函数
iterator Find(const K& key)
{
	Node* cur = _root;
	KeyOfT kot;
	while (cur)
	{
		if (kot(cur->_data) < key)
		{
			cur = cur->_right;//查询的值 > 节点值,-》右子树
		}
		else if (kot(cur->_data) > key)
		{
			cur = cur->_left;//查询的值 < 节点值,-》左子树
		}
		else
		{
			return iterator(cur);
		}
	}
	return End();
}


十、红黑树(修改版)源码链接

链接:RBTree.h · wei/cplusplus - 码云 - 开源中国 (gitee.com)

实现好了红黑树,接下来就可以套用红黑树继而实现map和set的相关功能函数。


十一、set、map模拟实现代码

1.set的代码

namespace set_realize
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		const_iterator begin() const
		{
			return _t.begin();
		}

		const_iterator end() const 
		{
			return _t.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
				return pair<iterator, bool>(ret.first, ret.second);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};

	void test_set()
	{
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		set<int> s;
		for (auto e : a)
		{
			s.insert(e);
		}

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

2.map的代码

namespace map_realize
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		const_iterator begin() const
		{
			return _t.begin();
		}

		const_iterator end() const
		{
			return _t.end();
		}

		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}

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

	void test_map()
	{
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		map<int, int> m;
		for (auto e : a)
		{
			m.insert(make_pair(e, e));
		}
		cout << endl;

		map<int, int>::iterator it = m.begin();
		while (it != m.end())
		{
			//it->first++;
			it->second++;
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;

		map<string, int> countMap;
		string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		for (auto& e : arr)
		{
            // 1、e不在countMap中,插入pair(e, int()),然后返回次数++
			// 2、e在countMap中,返回value(次数)的引用,次数++
			countMap[e]++;
		}

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
}

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

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

相关文章

java ssm人力资源系统Y3程序

1&#xff0e;系统登录&#xff1a;系统登录是员工访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括员工名、密码和验证码&#xff0c;然后对登录进来的员工判断身份信息&#xff0c;判断是管理员还是普通员工。 2&#xff0e;系统员工管理&#xff1a;不管是超级…

深入剖析:如何优化Android应用的性能和内存管理

深入剖析&#xff1a;如何优化Android应用的性能和内存管理 性能和内存管理的重要性 在今天的移动应用开发中&#xff0c;用户对于应用的性能和体验要求越来越高。一款性能卓越的Android应用能够提供流畅的操作体验、快速的响应速度以及较低的资源消耗&#xff0c;从而提高用户…

26《Protein Actions Principles and Modeling》-《蛋白质作用原理和建模》中文分享

​《Protein Actions Principles and Modeling》-《蛋白质作用原理和建模》 本人能力有限&#xff0c;如果错误欢迎批评指正。 第六章&#xff1a;The principles of protein folding kinetics &#xff08;蛋白质折叠动力学的原理&#xff09; -速率测量有助于深入了解蛋白…

高级数据结构与算法 | 基数树(Radix Tree)

文章目录RadixTree基本概念概念Radix Tree VS Trie Tree应用场景实现数据结构插入删除查找完整代码RadixTree 基本概念 概念 如果对 Trie 不太了解&#xff0c;可以看看我的往期博客&#xff1a; https://oreki.blog.csdn.net/article/details/109076473 Radix Tree是一种基于…

[HNCTF 2022 Week1]Challenge__rce

1.打开环境。 查看源码传入hint获得源码。 <?php error_reporting(0); if (isset($_GET[hint])) {highlight_file(__FILE__); } if (isset($_POST[rce])) {$rce $_POST[rce];if (strlen($rce) < 120) {if (is_string($rce)) {if (!preg_match("/[!#%^&*:\-&…

采集工具如何帮助SEO优化关键词

随着互联网的发展&#xff0c;越来越多的企业开始意识到SEO优化对于企业的重要性。SEO优化可以帮助企业提高网站在搜索引擎中的排名&#xff0c;进而吸引更多的潜在客户。而关键词则是SEO优化的核心&#xff0c;如何找到合适的关键词&#xff0c;成为了企业优化的关键。在这里&…

ActiveMQ使用(三):在JavaScript中使用mqttws31.js

ActiveMQ使用(三):在JavaScript中使用mqttws31.js 1. 环境准备 jQuery-1.10 下载地址:https://www.jsdelivr.com/package/npm/jquery-1.10.2?tabfilesmqttws31.js: 下载地址:https://www.jsdelivr.com/package/npm/ng2-mqtt 2. 相关代码 <!DOCTYPE html> <html …

【GPT4】微软 GPT-4 测试报告(3)GPT4 的编程能力

欢迎关注【youcans的GPT学习笔记】原创作品&#xff0c;火热更新中 微软 GPT-4 测试报告&#xff08;1&#xff09;总体介绍 微软 GPT-4 测试报告&#xff08;2&#xff09;多模态与跨学科能力 微软 GPT-4 测试报告&#xff08;3&#xff09;GPT4 的编程能力 【GPT4】微软 GPT-…

在线绘制思维导图

思维导图是一种可视化的思维工具&#xff0c;它可以将放射性思考具体化为可视的图像和图表。 思维导图利用图文并重的技巧&#xff0c;把各级主题的关系用相互隶属与相关的层级图表现出来&#xff0c;把主题关键词与图像、颜色等建立记忆链接。 它运用图像和颜色等多种元素&…

ctf做题记录本

2023年3月16日 1.XXE漏洞 没做出来&#xff0c;bp上怎么不显示结果 https://blog.csdn.net/weixin_43553654/article/details/107760067?spm1001.2101.3001.6650.5&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edefault%7EESLANDING%7Edefault-5-107760067-blo…

Java线上监控诊断产品Arthas

最近一直在研究Java的动态追踪技术&#xff0c;碰到了Arthas&#xff0c;正好以前也想学&#xff0c;趁此机会就了解了一下。 什么是Arthas&#xff1f;首先我们看看Arthas官方文档是怎么描述的&#xff1a; 什么是Arthas Arthas 是一款线上监控诊断产品&#xff0c;通过全局…

欧拉函数及其线性筛

一&#xff0c;定义 欧拉函数是对于n小于或者等于他的数中与n互质的数的个数。一般用φ(x)表示。 二&#xff0c;欧拉函数公式 其中pi为n的所有质因数。 公式的理解方法可以是pi是与n互质的数&#xff0c;那么它&#xff08;包括它的倍数&#xff09;在1~n里面是均匀出现的&…

分布式场景下,Apache YARN、Google Kubernetes 如何解决资源管理问题?

所有的资源管理系统都需要解决资源的有效利用、任务的有效响应、调度策略的灵活配置这三个最基本问题。那么在分布式的场景下&#xff0c;YARN和Kubernetes是怎么解决的呢&#xff1f;本篇进行介绍。 — Apache YARN — YARN全称为&#xff08;Yet Another Resource Negotiato…

OSPF开放式最短路径优先协议

目录标题OSPF协议OSPF的数据包---5种OSPF的状态机OSPF的工作过程OSPF的基本配置关于ospf协议从邻居建立成为邻接的条件ospf的接口网络类型OSPF协议 是是无类别链路状态型IGP协议&#xff1b;由于其基于拓扑进行更新收敛&#xff0c;故更新量会随着拓扑的变大而呈指数上升&…

处理CSV(python)

处理CSV&#xff08;python&#xff09;简介1. CSV和Python简介2. 文章内容简介一、用csv模块读取和写入CSV文件1. CSV模块2. 示例二、用pandas库读取和写入CSV文件1. pandas2. 示例三、处理CSV文件中的特殊情况1. 特殊情况及处理方法2. 示例简介 1. CSV和Python简介 CSV是一…

动态内存管理--从动态内存分配函数开始和你一起了解

目录前言1.为什么存在动态内存分配2.动态内存函数的介绍2.1malloc函数和free函数2.2calloc函数2.3realloc函数3.常见的动态内存错误3.1对NULL指针的解引用操作3.2对动态开辟空间的越界访问3.3对非动态内存开辟的内存使用free释放3.4使用free释放一块动态内存的一部分3.5对同一块…

【致敬未来的攻城狮计划】— 连续打卡第三天:欲速则不达,今天是对RA2E1 基础知识的补充学习。

系列文章目录 1.连续打卡第一天&#xff1a;提前对CPK_RA2E1是瑞萨RA系列开发板的初体验&#xff0c;了解一下 2.开发环境的选择和调试&#xff08;从零开始&#xff0c;加油&#xff09; 文章目录 目录 系列文章目录 文章目录 前言 一、RA是什么&#xff1f; 二、RA特点…

RHCE——shell脚本练习

一.实验要求 1、判断web服务是否运行&#xff08;1、查看进程的方式判断该程序是否运行&#xff0c;2、通过查看端口的方式判断该程序是否运行&#xff09;&#xff0c;如果没有运行&#xff0c;则启动该服务并配置防火墙规则。 ​2、使用curl命令访问第二题的web服务&#xff…

Kafka的历史版本对应SpringBoot版本

截至目前&#xff08;2023年&#xff09;&#xff0c;Kafka的最新版本是2.9.0&#xff0c;发布于2022年11月30日。Kafka的历史版本可以在Kafka官方网站的下载页面中找到。Kafka从0.8版本开始发布&#xff0c;经历了多个版本的迭代和升级。以下是一些比较重要的Kafka版本及其发布…

Python实现哈里斯鹰优化算法(HHO)优化Catboost回归模型(CatBoostRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 2019年Heidari等人提出哈里斯鹰优化算法(Harris Hawk Optimization, HHO)&#xff0c;该算法有较强的全…