【C++红黑树应用】模拟实现STL中的map与set

news2024/11/16 16:38:25

目录

  • 🚀 前言
  • 一: 🔥 红黑树的修改
  • 二: 🔥 红黑树的迭代器
  • 三: 🔥 perator++() 与 operator--()
  • 四: 🔥 红黑树相关接口的改造
    • ✨ 4.1 Find 函数的改造
    • ✨ 4.2 Insert 函数的改造
  • 五:🔥 Set 和 map的模拟实现
    • ✨ 5.1 Set的基本设计
    • ✨ 5.2 Map的基本设计
  • 六:📖 红黑树改造的完整代码及总结

🚀 前言

红黑树类的实现参考上一篇文章:【C++高阶数据结构】红黑树:全面剖析与深度学习
之前我们已经学习了如何手搓一棵红黑树,现在让我们来对红黑树进行改造,并且封装成map和set.

我们通过观察其stl源码切入进行仿写:
在这里插入图片描述
我们发现无论是map还是set都复用的一个红黑树,模板参数都是k,v模型通过源码我们可以发现:set -> rb_tree<k, k> | | map<k, v> -> rb_tree<k, pair<const k, v>>。所以节点中存什么内容是由v决定的,不是k决定的。将红黑树写成泛型,所以我们必须将之前写的红黑树的模板进行修改!

一: 🔥 红黑树的修改

【改造前】


template<class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node; // 红黑树节点
public:
	RBTree() :_root(nullptr) {}  // 构造函数
    bool Insert(const T& data);  // 插入节点
    // ...
private:
	Node* _root;
};

红黑树的插入节点接口中要先通过比较节点中数据的大小来查找适合插入的位置,但是红黑树并不知道数据 data 到底是 key 还是 pair。如果数据 data 是 key,那么直接取 key 来作比较;如果数据 data 是 pair,则需要取 first 来作比较。

  • 所以我们就得使用仿函数进行操作,我们创建keyoft仿函数取出T对象中的key即可

在这里插入图片描述
【改造后】

红黑树的定义

  • K:键值key的类型。
  • T:数据的类型,如果是 map,则为 pair<const K, V>;如果是 set,则为 K。
  • KeyOfT:通过 T 的类型来获取 key 值的一个仿函数类。
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node; // 红黑树节点
public:
	RBTree() :_root(nullptr) {}  // 构造函数
    bool Insert(const T& data);  // 插入节点(接口返回值目前是bool,后续要改为pair)
    // ...
private:
	Node* _root;
};

二: 🔥 红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代
器,需要考虑以前问题:begin()与end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,
可以得到一个有序的序列。

SGI-STL源码中,红黑树有一个哨兵位的头节点,begin() 是放在红黑树中最小节点(即最左侧节点)的位置,end() 是放在 end() 放在头结点的位置。

本文所采取的方式并没有使用头节点,而是通过其他方法实现,使用空指针代表end()


template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node; // 红黑树节点
public:
	typedef _RBTreeIterator<T, T&, T*> iterator; // 迭代器
    
    iterator begin() // begin(): 指向红黑树的最左节点的迭代器
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
        // 注意:单参数的构造函数支持隐式类型转换,节点会被构造成迭代器
        // 所以也可以这样写:return left;
	}
 
	iterator end() // end(): 指向nullptr的迭代器
	{
		return iterator(nullptr);
	}
private:
	Node* _root = nullptr;
};

三: 🔥 perator++() 与 operator–()

  1. operator++()
  • 右不为空,右子树的最左节点
  • 右为空,沿着到根的路径找孩子是父亲左的那个祖先
  1. operator–()
  • 左不为空,左子树的最右节点
  • 左为空,沿着到根的路径找孩子是父亲右的那个祖先

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		,_root(root)
	{}

	Self& operator++()
	{
		if (_node->_right)
		{
			// 右不为空,右子树最左节点就是中序下一个
			Node* leftMost = _node->_right;
			while (leftMost->_left)
			{
				leftMost = leftMost->_left;
			}

			_node = leftMost;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node == nullptr)            // end()情况特殊处理
		{
			// end()--  走到最右节点
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
			{
				rightMost = rightMost->_right;
			}

			_node = rightMost;
		}
		else if (_node->_left)
		{
			// 右不为空,右子树最左节点就是中序下一个
			Node* rightMost = _node->_left;
			while (rightMost->_right)
			{
				rightMost = rightMost->_right;
			}

			_node = rightMost;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};
}

四: 🔥 红黑树相关接口的改造

✨ 4.1 Find 函数的改造

查找成功,返回查找到的那个节点的迭代器,查找失败,就返回 nullptr。

Iterator Find(const K& key)
{
	KeyOfT kot;              // 仿函数类的对象
	Node* cur = _root;
	while (cur)
	{
		if (kot(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else if (kot(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else
		{
			return Iterator(cur, _root);
		}
	}

	return End();
}

✨ 4.2 Insert 函数的改造

注意:map 里的 operator[] 需要依赖 Insert 的返回值

pair<Iterator, bool> Insert(const T& data)
{
	if (_root == nullptr) {
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(Iterator(_root, _root), true);
	}

	KeyOfT kot;        // 仿函数
	// 找到插入位置
	Node* cur = _root, * parent = nullptr;
	while (cur)
	{
		if (kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return make_pair(Iterator(cur, _root), false);
		}
	}
	cur = new Node(data);
	Node* newnode = cur;
	//	新增节点 颜色优先选择红色
	cur->_col = RED;
	if (kot(data) > kot(parent->_data)) parent->_right = cur;
	else parent->_left = cur;

	cur->_parent = parent;

	// 1、parent不存在,cur就是根了,出去后把根处理成黑的
	// 2、parent存在,且为黑
	// 3、parent存在,且为红,继续循环处理
	// 变色了之后持续网上处理
	while (parent && parent->_col == RED)          // 父亲颜色是红色就需要继续处理(来连续的红节点, 关键看叔叔)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)               // 父亲在爷爷的左边 右边就是对称的
		{
			Node* uncle = grandfather->_right;
			//    g
			//  p   u
			if (uncle && uncle->_col == RED)     // 如果叔叔存在且为红色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = grandfather->_parent;
			}
			else {                               // 叔叔存在且为黑或者不存在  那么旋转+变色
				//    g
				//  p   u
				// c
				// 单旋
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else {
					//    g
					//  p   u
					//    c
					// 双旋
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;                           // 局部根节点是黑色那么就可以退出了
			}
		}
		else {
			//    g
			//  u   p
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)     // 如果叔叔存在且为红色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = grandfather->_parent;
			}
			else {                               // 叔叔存在且为黑或者不存在  那么旋转+变色
				//    g
				//  u   p
				//        c
				// 单旋
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else {
					//    g
					//  u   p
					//    c
					// 双旋
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;                           // 局部根节点是黑色那么就可以退出了
			}
		}
	}
	_root->_col = BLACK;

	return make_pair(Iterator(newnode, _root), true);
}

五:🔥 Set 和 map的模拟实现

✨ 5.1 Set的基本设计

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。

为了解决 set 中 key 值不能修改的问题,在传给 RBTree 的第二个模板参数前加 const 即可。

#pragma once

#include"RBTree.h"

namespace bit
{
	template<class K>
	class set
	{   
		// 取出 keyofvalue 的仿函数类
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator 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)
		{
			return _t.Insert(key);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};

	void Print(const set<int>& s)
	{
		set<int>::const_iterator it = s.end();
		while (it != s.begin())
		{
			--it;
			//*it += 2;
			cout << *it << " ";
		}
		cout << endl;
	}

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

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

		set<int>::iterator it = s.end();
		while (it != s.begin())
		{
			--it;
			cout << *it << " ";
		}
		cout << '\n';
	}
}

✨ 5.2 Map的基本设计

#pragma once

#include"RBTree.h"

namespace bit
{
	template<class K, class V>
	class map
	{
		// 取出 keyofvalue 的仿函数类
		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>::ConstIterator 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<K, V>& kv)
		{
			return _t.Insert(kv);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

		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()
	{
		map<string, string> dict;
		dict.insert({ "sort", "排序" });
		dict.insert({ "left", "左边" });
		dict.insert({ "right", "右边" });

		dict["left"] = "左边,剩余";
		dict["insert"] = "插入";
		dict["string"];

		map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			// 不能修改first,可以修改second
			//it->first += 'x';
			it->second += 'x';

			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;
	}

}

六:📖 红黑树改造的完整代码及总结

#pragma once

#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#include <map>
#include <assert.h>

using namespace std;

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode {
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;		
	Color _col;

	RBTreeNode(const T data)
		:_data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		,_root(root)
	{}

	Self& operator++()
	{
		if (_node->_right)
		{
			// 右不为空,右子树最左节点就是中序下一个
			Node* leftMost = _node->_right;
			while (leftMost->_left)
			{
				leftMost = leftMost->_left;
			}

			_node = leftMost;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node == nullptr)            // end()情况特殊处理
		{
			// end()--  走到最右节点
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
			{
				rightMost = rightMost->_right;
			}

			_node = rightMost;
		}
		else if (_node->_left)
		{
			// 右不为空,右子树最左节点就是中序下一个
			Node* rightMost = _node->_left;
			while (rightMost->_right)
			{
				rightMost = rightMost->_right;
			}

			_node = rightMost;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

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

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

	bool operator!= (const Self& s)
	{
		return _node != s._node;
	}
};


// T可以是key 也可以是map 三个参数中第一个key是给find和 erase的   pair和第二个key是给insert的
template<class K, class T, class KeyOfT>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

	Iterator Begin()
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}

		return Iterator(leftMost, _root);
	}

	Iterator End()
	{
		return Iterator(nullptr, _root);
	}

	ConstIterator Begin() const
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}

		return ConstIterator(leftMost, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}

	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = Copy(t._root);
	}

	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		swap(_root, t._root);
		return *this;
	}

	~RBTree()
	{
		Destroy(_root);
		_root = nullptr;
	}


	pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr) {
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(Iterator(_root, _root), true);
		}

		KeyOfT kot;        // 仿函数
		// 找到插入位置
		Node* cur = _root, * parent = nullptr;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(Iterator(cur, _root), false);
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		//	新增节点 颜色优先选择红色
		cur->_col = RED;
		if (kot(data) > kot(parent->_data)) parent->_right = cur;
		else parent->_left = cur;

		cur->_parent = parent;

		// 1、parent不存在,cur就是根了,出去后把根处理成黑的
		// 2、parent存在,且为黑
		// 3、parent存在,且为红,继续循环处理
		// 变色了之后持续网上处理
		while (parent && parent->_col == RED)          // 父亲颜色是红色就需要继续处理(来连续的红节点, 关键看叔叔)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)               // 父亲在爷爷的左边 右边就是对称的
			{
				Node* uncle = grandfather->_right;
				//    g
				//  p   u
				if (uncle && uncle->_col == RED)     // 如果叔叔存在且为红色
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else {                               // 叔叔存在且为黑或者不存在  那么旋转+变色
					//    g
					//  p   u
					// c
					// 单旋
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else {
						//    g
						//  p   u
						//    c
						// 双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;                           // 局部根节点是黑色那么就可以退出了
				}
			}
			else {
				//    g
				//  u   p
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)     // 如果叔叔存在且为红色
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else {                               // 叔叔存在且为黑或者不存在  那么旋转+变色
					//    g
					//  u   p
					//        c
					// 单旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else {
						//    g
						//  u   p
						//    c
						// 双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;                           // 局部根节点是黑色那么就可以退出了
				}
			}
		}
		_root->_col = BLACK;

		return make_pair(Iterator(newnode, _root), true);
	}

	Iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return Iterator(cur, _root);
			}
		}

		return End();
	}

	Node* Copy(Node * root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_kv);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);

			return newRoot;
		}

	void Destroy(Node * root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

	void InOrder()
	{
		_InOrder(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	// 检查是否是红黑树
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
		{
			return false;
		}

		// 参考值
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}

			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << '\n';
			return false;
		}

		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
	}

	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	void RotateL(Node * parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if (subRL) subRL->_parent = parent;

			Node* parent_parent = parent->_parent;

			subR->_left = parent;
			parent->_parent = subR;

			if (parent_parent == nullptr)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else {
				if (parent == parent_parent->_left) parent_parent->_left = subR;
				else parent_parent->_right = subR;

				subR->_parent = parent_parent;
			}
		}


	void RotateR(Node * parent)
		{
			Node* subL = parent->_left;
			Node* subLR = parent->_left->_right;

			parent->_left = subLR;
			if (subLR) subLR->_parent = parent;

			Node* parent_parent = parent->_parent;

			subL->_right = parent;
			parent->_parent = subL;


			if (parent_parent == nullptr)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else {
				if (parent == parent_parent->_left)
				{
					parent_parent->_left = subL;
				}
				else {
					parent_parent->_right = subL;
				}
				subL->_parent = parent_parent;
			}
		}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << '\n';
		_InOrder(root->_right);
	}


	Node* _root = nullptr;

};

//void TestRBTree1()
//{
//	RBTree<int, int> t;
//	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//	// int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//	for (auto e : a)
//	{
//
//		t.Insert({ e, e });
//	}
//
//	t.InOrder();
//	cout << t.IsBalance() << endl;
//}

以上就是红黑树的改造及封装map和set全部内容,需要我们好好掌握,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

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

相关文章

推荐珍藏已久的 3 款优质电脑软件,每一款都值得拥有

Advanced Find and Replace Advanced Find and Replace是一款功能强大的文本查找和替换工具&#xff0c;能够高效地在多个文档中进行复杂的内容操作。它支持通配符和正则表达式&#xff0c;使得用户可以精确地定位和替换特定的文本内容。该软件不仅适用于普通文本文件&#xff…

防洪评价报告编制方法与水流数学模型建模技术

原文链接&#xff1a;防洪评价报告编制方法与水流数学模型建模技术https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247610610&idx2&sn432d30cb40ec36160d635603c7f22c96&chksmfa827115cdf5f803ddcaa03a21e3721d6949d6a336062bb38170e3f9d5bd4d391cc36cc…

【速记!】3DMAX的50个常用快捷键

分享一组基本的3dMax动画和建模快捷键&#xff0c;以用于你的建筑项目。 3dMax是创建三维模型和动画的设计师中流行的软件。它用于建筑、电子游戏或其他需要高清晰度和高精度图形的视觉项目&#xff0c;是视觉艺术家寻找新工具的理想伴侣&#xff0c;这些工具可以帮助他们详细…

Vue3实战案例 知识点全面 推荐收藏 超详细 及附知识点解读

最近经常用到vue中的一些常用知识点&#xff0c;打算系统性的对 vue3 知识点进行总结&#xff0c;方便自己查看&#xff0c;另外也供正在学习 vue3 的同学参考&#xff0c;本案例基本包含 Vue3所有的基本知识点&#xff0c;欢迎参考&#xff0c;有问题评论区留言&#xff0c;谢…

Linux基本功能

Linux 操作系统&#xff0c;作为开源社区的明星之一&#xff0c;以其稳定性、安全性和灵活性在全球范围内得到广泛应用。 1. 多用户和多任务支持 Linux 是一个真正的多用户系统&#xff0c;允许多个用户同时登录并在同一时间内运行多个程序。每个用户拥有自己的账户和权限&…

每日OJ_牛客HJ86 求最大连续bit数

目录 牛客HJ86 求最大连续bit数 解析代码 牛客HJ86 求最大连续bit数 求最大连续bit数_牛客题霸_牛客网 解析代码 根据位运算&#xff0c;获取每一位的二进制值。获取第i位的值&#xff1a; (n >> i) & 1或者 n & (1 << i)。如果1连续&#xff0c;则计数…

Redis 安装和数据类型

Redis 安装和数据类型 一、Redis 1、Redis概念 redis 缓存中间件&#xff1a;缓存数据库 nginx web服务 php 转发动态请求 tomcat web页面&#xff0c;也可以转发动态请求 springboot 自带tomcat 数据库不支持高并发&#xff0c;一旦访问量激增&#xff0c;数据库很快就…

网工内推 | 合资公司、上市公司数据库工程师,OCP/OCM认证优先,双休

01 欣旺达电子股份有限公司 &#x1f537;招聘岗位&#xff1a;数据库管理高级工程师 &#x1f537;岗位职责&#xff1a; 1、负责数据库规划、管理、调优工作&#xff1b; 2、负责数据库应急预案制定、应急预案维护和应急支持&#xff1b; 3、负责数据库异常处理&#xff…

Unity UGUI 之 事件触发器

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 本文在发布时间选用unity 2022.3.8稳定版本&#xff0c;请注意分别 1.什么是UI事件触发器…

linux安装jdk和jps(为rocketMq准备)

20240730 一、安装rocketMq之前的准备工作1. 安装jkd&#xff08;这里以1.8为例子&#xff09;1.1 下载jdk1.81.2 上传到linux&#xff08;拖拽&#xff09;1.3 解压1.4 配置环境变量1.5 使配置文件生效1.6 验证结果 2. JPS2.1 解决 一、安装rocketMq之前的准备工作 1. 安装jk…

angular入门基础教程(十)管道即过滤器

管道 何为管道&#xff0c;ng 翻译的真烂&#xff0c;但是听多了你就理解了&#xff0c;类似于 vue2 中的过滤器&#xff0c;过滤器在 vue3 中已经废弃 从common包里面引入&#xff0c;并注册 import { Component, inject } from "angular/core"; import { UpperC…

C# 调用Webservice接口接受数据测试

1.http://t.csdnimg.cn/96m2g 此链接提供测试代码&#xff1b; 2.http://t.csdnimg.cn/64iCC 此链接提供测试接口&#xff1b; 关于Webservice的基础部分不做赘述&#xff0c;下面贴上我的测试代码&#xff08;属于动态调用Webservice&#xff09;&#xff1a; 1&#xff…

Appium自动化测试 ------ 常见模拟操作!

Appium自动化测试中的常见模拟操作涵盖了多种用户交互行为&#xff0c;这些操作对于自动化测试框架来说至关重要&#xff0c;因为它们能够模拟真实用户的使用场景&#xff0c;从而验证应用程序的功能和稳定性。 以下是一些Appium自动化测试中常见的模拟操作&#xff1a; 基本操…

XPathParser类

XPathParser类是mybatis对 javax.xml.xpath.XPath的包装类。 接下来我们来看下XPathParser类的结构 1、属性 // 存放读取到的整个XML文档private final Document document;// 是否开启验证private boolean validation;// 自定义的DTD约束文件实体解析器&#xff0c;与valida…

JavaSE面向对象进阶

static 介绍 static表示静态&#xff0c;是Java中的一个修饰符可以修饰成员方法、成员变量 被static修饰的成员变量&#xff0c;叫做静态变量被static修饰的成员方法&#xff0c;叫做静态方法 静态变量 特点&#xff1a;被该类所有对象共享 调用方式&#xff1a; 类名调用&am…

关于@Async

Spring Boot 2.x 开始&#xff0c;默认情况下&#xff0c;Spring AOP 使用 CGLIB 代理 Async不能在同一个类中直接调用 关于在控制器不能使用Async 并不是因为SpringBoot2以前使用JDK代理 因为JDK代理需要类实现接口,控制器没有实现接口等原因 真正原因是 Async 不能…

windows@powershell@任务计划@自动任务计划@taskschd.msc.md

文章目录 使用任务计划windows中的任务计划任务计划命令行程序开发windows 应用中相关api传统图形界面FAQ schtasks 命令常见用法创建计划任务删除计划任务查询计划任务修改计划任务运行计划任务 PowerShell ScheduledTasks常用 cmdlet 简介1. Get-ScheduledTask2. Register-Sc…

手动在ubuntu上搭建一个nginx,并安装证书的最简化完整过程

背景&#xff1a;由于想做个测试&#xff1a;即IP为A的服务器&#xff0c;绑定完域名X后&#xff0c;如果再绑定域名Y&#xff0c;能不能被访问到。&#xff08;假设对A不做绑定域名的设置&#xff09; 这个问题的来源&#xff0c;见上一篇文章&#xff1a;《云服务器被非法域名…

kaggle使用api下载数据集

背景 kaggle通过api并配置代理下载数据集datasets 步骤 获取api key 登录kaggle&#xff0c;点个人资料&#xff0c;获取到自己的api key 创建好的key会自动下载 将key放至家目录下的kaggle.json文件中 我这里是windows的administrator用户。 装包 我用了虚拟环境 pip …

021.自定义指纹浏览器编译-修改ClientRects指纹

一、什么是ClientRects指纹 ClientRects指纹获取的核心方法是DOM元素方法getClientRects()​ 。getClientRects()​ 可以返回一个元素的所有 CSS 边界框&#xff08;ClientRect对象数组&#xff09;&#xff0c;包括其大小、位置等信息。每个边界框由其左上角的 x, y 坐标和宽…