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

news2024/11/25 2:15:03

红黑树里面具体存的是什么类型的元素,是由模板参数 T 来决定

  • 如果 T 是 Key 那么就是 set。

  • 如果 T 是 pair<const Key, V>,那么就是 map。

1、定义红黑树的节点结构

// 定义红黑颜色
enum Colour
{
	RED,
	BLACK
};

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

2、 定义红黑树结构

定义红黑树结构(KV

  • K:键值 key 的类型。
  • T:数据的类型,如果是 map,则为 pair<const K, V>;如果是 set,则为 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 来作比较。

【思考】这该如何去实现对传进来的不同类型的数据都能进行比较呢?

STL 源码是这样实现的,通过传给模板参数 KeyOfValue 的是 set 的仿函数还是 map 的仿函数来应对不同类型数据的比较:


【改造后】

改造后的红黑树结构(增加了仿函数类):(还没完善好,还差迭代器,insert 和 operator[] 接口还没实现)

我们自己写的代码,通过给红黑树增加一个模板参数 KeyOfT,KeyOfT 是一个仿函数类,把 map 和 set 中实现的仿函数传给 KeyOfT,根据传的不同数据类型 T (key / pair) 和该类型对应的仿函数 (SetKey / MapFirst),调用仿函数取出要比较的值(key / first),来进行比较。

红黑树的定义

  • 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;
};

【画图说明】

通过 T 的类型和对应的取 T 类型对象的值的仿函数,就可以进行不同类型数据的比较了:

#pragma once

enum Colour
{
	RED,
	BLACK
};

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

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

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

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

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			// 下一个就是右子树的最左节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		else
		{
			// 找祖先里面孩子不是祖先的右的那个
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			// 下一个是左子树的最右节点
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}
			_node = right;
		}
		else
		{
			// 孩子不是父亲的左的那个祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

template<class K, class T, class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;

	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	pair<iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;

		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		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), false);
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		cur->_col = RED;

		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			// 关键看叔叔
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;
				// 情况1:uncle存在且为红,变色+继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}// 情况2+3:uncle不存在+存在且为黑
				else
				{
					// 情况2:右单旋+变色
					//     g 
					//   p   u
					// c
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
						// 情况3:左右单旋+变色
						//     g 
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else // (parent == grandfater->_right)
			{
				Node* uncle = grandfater->_left;
				// 情况一
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
					// 情况2:左单旋+变色
					//     g 
					//   u   p
					//         c
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
						// 情况3:右左单旋+变色
						//     g 
						//   u   p
						//     c
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;
		/*Node* cur = _root;
		while (cur)
		{
		if (cur->_col == BLACK)
		++benchmark;

		cur = cur->_left;
		}*/
		return PrevCheck(_root, 0, benchmark);
	}

private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			//return;
			if (benchmark == 0)
			{
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

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

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

		return PrevCheck(root->_left, blackNum, benchmark)
			&& PrevCheck(root->_right, blackNum, benchmark);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

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

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

		Node* ppNode = parent->_parent;

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

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

			subR->_parent = ppNode;
		}
	}

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

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

		Node* ppNode = parent->_parent;

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

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

			subL->_parent = ppNode;
		}
	}

private:
	Node* _root = nullptr;
};

一、红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。map 和 set 的迭代器是封装的红黑树的迭代器。

如果想要给红黑树增加迭代器,需要考虑以前问题:

1、begin() 与 end()

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

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

begin() 可以放在红黑树中最小节点(即最左侧节点)的位置,end() 放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?

因为这里我们只是对它进行模拟,理解它的底层原理即可,为了不让代码太过复杂,我们这里的模拟实现就不设定 header 结点,直接让 end() 为 nullptr 即可。

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

2、operator++()与operator--()  

按照中序遍历(左子树 -- 根 -- 右子树) 来走,分为以下几种情况: 

1、it 指向节点的右子树不为空

  • 则 it++ 要访问的节点是,右子树中序(左子树 -- 根 -- 右子树)的第一个节点,也就是右子树中的最左节点(即最大节点)。


2、it 指向节点的右子树为空(说明以 it 为根的子树已经访问完了),且 it 的父亲存在,it 是它父亲的右孩子(说明 it 被访问完之后,以 it 父亲为根的子树也就访问完了,此时该访问 it 父亲的父亲了)

  • 则 it++ 要访问的节点是:it 指向节点的父亲的父亲(即节点 13)。

3、it 指向节点的右子树为空,且 it 父亲存在,it 是它父亲的左孩子

  • 则 it++ 要访问的节点是:it 指向节点的父亲节点(即节点 17)。

【注意】

当 it 访问完最后一个节点后,最后一个节点右子树为空,此时整棵树已经访问完了,cur 和 parent 会一直迭代走到根节点,然后返回 _node = parent,parent为空,我们在红黑树中 end() 的值给的也是空,这样当 it 访问完最后一个节点后,就等于 end() 了。 


  • T:数据的类型,如果是 map,则为 pair<const K, V>;如果是 set,则为 K。
  • Ref:数据的引用。
  • Ptr:数据的指针。
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node; // 红黑树节点
	typedef __RBTreeIterator<T, Ref, Ptr> Self; // 迭代器
	Node* _node; // 节点指针

    // 构造函数
	__RBTreeIterator(Node* node)
		:_node(node)
	{}

    // 运算符重载
	Ref operator*()
	{
		return _node->_data; // 返回当前迭代器指向节点中数据的引用
	}

	Ptr operator->()
	{
		return &_node->_data; // 返回当前迭代器指向节点中数据的地址
	}

    // 比较两个迭代器,即比较它们的节点指针,看是否指向同一节点
	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

    // 比较两个迭代器,即比较它们的节点指针,看是否指向同一节点
	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}

	Self& operator++() // 前置++
	{
        // 按照中序来走,分为两种情况:
        // 1、当前节点右子树不为空,则++访问右子树的最大节点
		if (_node->_right != nullptr)
		{
			// 下一个就是右子树的最左节点
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
        // 2、当前节点右子树为空
		else
		{
			// 找祖先里面孩子不是祖先的右的那个
			Node* parent = _node->_parent;
			Node* cur = _node;

            // (1)cur父亲存在且cur是父亲的右孩子,则++访问cur的父亲的父亲
            // (2)cur父亲存在且cur是父亲的左孩子,则++访问cur的父亲
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent; // 现在的parent就是我们要访问的位置
		}
		return *this; // 返回下一个节点的迭代器的引用
	}

    Self& operator--() // 前置--
	{
		if (_node->_left)
		{
			// 下一个是左子树的最右节点
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}
			_node = right;
		}
		else
		{
			// 孩子不是父亲的左的那个祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this; // 返回上一个节点的迭代器的引用
	}
};

二、map和set的插入

map 和 set 的 insert 是封装的红黑树的插入节点接口,所以我们先要模拟实现红黑树的插入。

功能:插入元素时,先通过该元素的 key 查找并判断该元素是否已在树中:

  • 如果在,返回:pair<指向该元素的迭代器, false>。
  • 如果不在,先插入节点,再返回:pair<指向该元素的迭代器, true>。
  • T:数据的类型,如果是 map,则为 pair<const K, V>;如果是 set,则为 K。
pair<iterator, bool> Insert(const T& data)
{
    //KeyOfT kot; // 实例化仿函数对象

    if (_root == nullptr)
    {
        _root = new Node(data); // 插入新节点
        _root->_col = BLACK; // 根节点为黑色

        return make_pair(iterator(_root), true); // 返回<指向插入节点的迭代器, true>
    }

    Node* parent = nullptr;
    Node* cur = _root; // 记录当前节点和它的父节点
    
    while (cur) // cur为空时,说明找到插入位置了
    {
        if (KeyOfT()(data) > KeyOfT()(cur->_data)) // 键值大于当前节点
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (KeyOfT()(data) < KeyOfT()(cur->_data)) // 键值小于当前节点
        {
            parent = cur;
            cur = cur->_left;
        }
        else // (KeyOfT()(data) == KeyOfT()(cur->_data)) // 键值等于当前节点
        {
            // 不允许数据冗余
            return make_pair(iterator(cur), false); // 返回<指向已有节点的迭代器, false>
        }
    }

    // 插入新节点,颜色为红色(可能会破坏性质3,产生两个连续红色节点)
    cur = new Node(data);
    Node* newnode = cur; // 保存下插入的新节点的位置
    cur->_col = RED;

    // 判断新节点是其父亲的左孩子还是右孩子
    if (KeyOfT()(cur->_data) > KeyOfT()(parent->_data))
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur; 
    }
    cur->_parent = parent; // 更新cur的双亲指针

    while (parent && parent->_col == RED)
	{
		Node* grandfater = parent->_parent;
		assert(grandfater);
		assert(grandfater->_col == BLACK);
		// 关键看叔叔
		if (parent == grandfater->_left)
		{
			Node* uncle = grandfater->_right;
			// 情况1:uncle存在且为红,变色+继续往上处理
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfater->_col = RED;
				// 继续往上处理
				cur = grandfater;
				parent = cur->_parent;
			}// 情况2+3:uncle不存在+存在且为黑
			else
			{
				// 情况2:右单旋+变色
				//     g 
				//   p   u
				// c
				if (cur == parent->_left)
				{
					RotateR(grandfater);
					parent->_col = BLACK;
					grandfater->_col = RED;
				}
				else
				{
					// 情况3:左右单旋+变色
					//     g 
					//   p   u
					//     c
					RotateL(parent);
					RotateR(grandfater);
					cur->_col = BLACK;
					grandfater->_col = RED;
				}

				break;
			}
		}
		else // (parent == grandfater->_right)
		{
			Node* uncle = grandfater->_left;
			// 情况一
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfater->_col = RED;
				// 继续往上处理
				cur = grandfater;
				parent = cur->_parent;
			}
			else
			{
				// 情况2:左单旋+变色
				//     g 
				//   u   p
				//         c
				if (cur == parent->_right)
				{
					RotateL(grandfater);
					parent->_col = BLACK;
					grandfater->_col = RED;
				}
				else
				{
					// 情况3:右左单旋+变色
					//     g 
					//   u   p
					//     c
					RotateR(parent);
					RotateL(grandfater);
					cur->_col = BLACK;
					grandfater->_col = RED;
				}
				break;
			}
		}
	}
    _root->_col = BLACK;
    return make_pair(iterator(newnode), true); // 返回<指向插入节点的迭代器, true>
}

三、map 的模拟实现

map 的底层结构就是红黑树,因此在 map 中直接封装一棵红黑树,然后将其接口包装下即可。

namespace xyl
{
    template<class K, class V>
    class map
    {
        // 作用:将value中的key提取出来
        struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first; // 返回pair对象中的key
			}
		};

    public:
        // 编译到这里的时候,类模板RBTree<K, pair<const K, V>, MapFirst>可能还没有实例化
        // 那么编译器就不认识这个类模板,更别说去它里面找iterator了
		// 所以要加typename,告诉编译器这是个类型,等它实例化了再去找它
        typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

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

        pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv); // 底层调用红黑树的接口
		}

        // 功能:传入键值key,通过该元素的key查找并判断是否在map中:
		// 1、在map中,返回key对应的映射值的引用
		// 2、不在map中,插入pair<key, value()>,再返回key对应映射值的引用
        V& operator[](const K& key)
		{
            // 注意:这里的V()是缺省值,调用V类型的默认构造函数去构造一个匿名对象
			pair<iterator, bool> ret = insert(make_pair(key, V()));

			return ret.first->second; // 返回key对应映射值的引用
		}

    private:
        RBTree<K, pair<K, V>, MapKeyOfT> _t; // 红黑树
    };
}

四、set 的模拟实现

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

namespace xyl
{
    template<class K>
    class set
    {
        // 作用是:将value中的key提取出来
        struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key; 返回key对象中的Key
			}
		};

    public:
        // 编译到这里的时候,类模板RBTree<K, K, SetKey>可能还没有实例化
		// 那么编译器就不认识这个类模板,更别说去它里面找iterator了
		// 所以要加typename,告诉编译器这是个类型,等它实例化了再去找它
        typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; // 红黑树类型重命名

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

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

        // 插入元素(key)
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key); // 底层调用红黑树的接口
		}

        // 中序遍历
		void inorder()
		{
			_t.InOrder();
		}

    private:
        RBTree<K, K, SetKeyOfT> _t; // 红黑树
    };
}

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

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

相关文章

memcpy()之小端模式

函数原型 void memcpy(voiddestin, const void *src, size_t n); 功能 由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。 头文件 #include<string.h> 返回值 函数返回一个指向dest的指针。 例1&#xff1a;如果用来复制字…

wordpress版本识别

wordpress版本识别 1.通过RSS Feed识别 RSS Feed参考 访问网站/feed或者?feedrss 例如 默认结构&#xff1a;https://www.example.com/?feedrss2 其他结构&#xff1a;https://www.example.com/feed/ 返回结果中搜索 generator 可以看到直接是5.9.7版本 2.wpscan等工具扫一…

Java8 Stream API全面解析——高效流式编程的秘诀

文章目录 什么是 Stream Api?快速入门流的操作创建流中间操作filter 过滤map 数据转换flatMap 合并流distinct 去重sorted 排序limit 限流skip 跳过peek 操作 终结操作forEach 遍历forEachOrdered 有序遍历count 统计数量min 最小值max 最大值reduce 聚合collect 收集anyMatch…

用WebStorm运行VUE项目

提示&#xff1a;原来用VS Code开Vue&#xff0c;可是VS Code用Ctrl打不开国际化&#xff0c;下载推荐插件也不好使 文章目录 下载WebStorm运行WebStorm实用插件 下载WebStorm 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; https://www.jetbrains.com/webs…

国家管网集团古浪—河口天然气联络管道工程实现投产

10月26日&#xff0c;国家管网集团古浪—河口天然气联络管道工程实现“绿色”投产&#xff0c;进一步完善西北地区多通道供气网络&#xff0c;对于推动甘肃省“一核三带”新发展格局构建、提高“全国一张网”在甘青地区的供应保障能力具有重要意义。 古浪—河口天然气联络管道…

SpringBoot条件注解底层原理

一、简介 相信大家在学springBoot自动装配的时候会遇到下面这些条件注解&#xff0c;当存在其指定的条件&#xff0c;才会把对应的bean注入到IOC容器中。本博客旨在揭开其神秘的面纱&#xff01; 其实说白了&#xff0c;springBoot的这些注解只是针对普通spring框架中Conditi…

JSP 学生成绩查询管理系统eclipse开发sql数据库serlvet框架bs模式java编程MVC结构

一、源码特点 JSP 学生成绩查询管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;比较流行的servlet框架系统具有完整的源代码和数据库&#xff0c;eclipse开发系统主要采用B/S模式 开发。 java 学生成绩查询管理系统 代码下载链接…

CorelDRAW2023最新版本号24.5.0.731

CDR2023是一款近年来备受瞩目的工具软件&#xff0c;它提供了数据存储、分析以及处理的能力。但是&#xff0c;对于许多用户来说&#xff0c;CDR2023到底好用不好用还需要进行深入的分析和探讨。在本文中&#xff0c;我们将从多个角度分析CDR2023这款软件。 CorelDRAW2023版win…

【JMeter参数化】上一个接口返回作为下一个接口入参

前言: 实际工作场景当中,比如获取到商品列表,并查看商品详情。如果将商品id写死,就很笨拙。所以我们可以进行参数化动态去更新商品id 目录 场景1:接口A仅取一个值,作为接口B的入参 场景:接口A是获取教师列表中某个教室的id,接口B是查看该教师的详情页步骤: 1.获取接口…

DC系列 DC:4

DC:4 靶机 文章目录 DC:4 靶机信息收集IP端口收集网页信息收集网站信息收集bp爆破 反弹shellnc反弹 内网信息收集收集jim用户的密码密码爆破 Charles用户密码收集 提权teehee提权 备注 信息收集 IP端口收集 用arp-scan 网段锁定ip nmap对端口服务进行详细扫描nmap -p- -sV …

图片复制上传,拖拽输入框上传,el-upload自定义上传方法(上传和备注框强关联)

1. 效果图&#xff1a; 2. 复制图片使用的方法&#xff1a; 1.通过监听paste方法&#xff0c;获取复制内容2.获取复制内容中的clipboardData3.获取file文件进行上传 <input paste.native"handlePaste" />handlePaste(value){let files value.clipboardData…

win10 + vs2017 + cmake3.17编译tiff-4.0.9

前言&#xff1a; 需要先编译依赖库&#xff1a;zlib-1.2.11, jpeg-9b 我的安装根目录是&#xff1a;D:\Depend_3rd_party\tiffx64 1. 下载tiff-4.0.9.zip&#xff0c;并解压到根目录下 得到&#xff1a;D:\Depend_3rd_party\tiffx64\tiff-4.0.9 2. 创建build文件夹&#xff…

链栈的练习

链栈练习 相关内容&#xff1a;栈的链式存储结构&#xff08;链栈&#xff09; //链栈的初始化、判空、入栈、出栈、读取栈顶元素 //链栈的结点&#xff1a;数据域、指针域 #include<stdio.h> #include<stdlib.h> typedef int Status; #define OK 1 #define ERRO…

二维码智慧门牌管理系统升级:实现一标六实数据管理

文章目录 前言一、一标六实数据管理应用的关键特点二、一标六实数据管理应用的具体应用 前言 随着城市的发展&#xff0c;对基础数据的精确管理和快速查询需求日益增强。为满足这一需求&#xff0c;二维码智慧门牌管理系统进行了升级&#xff0c;提供一标六实数据管理应用&…

全自动洗衣机什么牌子好?内衣洗衣机品牌排行榜前四名

随着内衣洗衣机的流行&#xff0c;很多小伙伴在纠结该不该入手一款内衣洗衣机&#xff0c;专门来洗一些贴身衣物&#xff0c;答案是非常有必要的&#xff0c;因为我们现在市面上的大型洗衣机只能做清洁&#xff0c;无法对我们的贴身衣物进行一个高强度的清洁&#xff0c;而小小…

[自定义 Vue 组件] 小尾巴顶部导航栏(2.0) TailTopNav

文章归档&#xff1a;https://www.yuque.com/u27599042/coding_star/oglrqteg8fzvvzn0 [自定义 Vue 组件] 响应式顶部导航栏(1.0) TopNav&#xff1a;https://www.yuque.com/u27599042/coding_star/hzltsltxgavwx8u2 组件效果示例 组件所依赖的子组件 [自定义 Vue 组件] 小尾巴…

node插件express(路由)的基本使用(一)

文章目录 一、express的了解1.定义2.作用3.使用express的前提条件&#xff08;1&#xff09;如果是新文件夹需要薪资package.json文件&#xff0c;如果有就忽略&#xff08;2&#xff09;安装第三方依赖包&#xff08;3&#xff09;在使用的地方导入express 二、express的基本使…

原地封神!一个只用套模板即可制作电子相册的网站

对于忙碌的年轻人来说&#xff0c;一键操作的模板意味着无需复杂的操作步骤&#xff0c;就能轻松制作出精美的电子相册。 但是一个好的工具也是事关重要&#xff0c;最近发现了一款非常适合年轻人的模板---FLBOOK在线制作电子杂志平台&#xff0c;只需要找到合适的模板即可制作…

【leetcode】26. 删除有序数组中的重复项(图解)

目录 1. 思路&#xff08;图解&#xff09;2. 代码 题目链接&#xff1a; leetcode 26. 删除有序数组中的重复项 题目描述&#xff1a; 注意返回的是去重后的数组长度&#xff0c;但是输出的是去重后的数组元素。 1. 思路&#xff08;图解&#xff09; 思路&#xff1a;快慢…

【数据结构】深入浅出理解快速排序背后的原理 以及 版本优化【万字详解】(C语言实现)

快速排序 快速排序递归实现前言一、Hoare版本&#xff08;一&#xff09;算法运行图例&#xff08;二&#xff09;算法核心思路&#xff08;三&#xff09;算法实现步骤&#xff08;1&#xff09;单趟&#xff08;2&#xff09;多趟 &#xff08;四&#xff09;码源详解 递归实…