【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树

目录

1 -> 红黑树

1.1 -> 红黑树的概念

1.2 -> 红黑树的性质

1.3 -> 红黑树节点的定义

1.4 -> 红黑树的结构

1.5 -> 红黑树的插入操作

1.6 -> 红黑树的验证

1.8 -> 红黑树与AVL树的比较

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

2.2 -> 改造红黑树

2.3 -> map的模拟实现

2.4 -> set的模拟实现


1 -> 红黑树

1.1 -> 红黑树的概念

红黑树,是一种二叉搜索树,但在每个节点上增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。

1.2 -> 红黑树的性质

  1. 每个节点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的。
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  5. 每个叶子节点都是黑色的(此处的叶子节点指空节点)。

1.3 -> 红黑树节点的定义

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;

// 节点的颜色
enum Color 
{ 
	RED, BLACK 
};

// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
	RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _data(data), _color(color)
	{}
	RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子
	RBTreeNode<ValueType>* _pRight;  // 节点的右孩子
	RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)

	ValueType _data; // 节点的值域
	Color _color;    // 节点的颜色
};

1.4 -> 红黑树的结构

为了后续实现关联式容器更加简单,红黑树的实现中增加一个头节点,因为根节点必须是黑色的,为了与根节点区分开,将头节点给成黑色,并且让头节点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

1.5 -> 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可以分为两步:

1. 按照二叉搜索树的树规则插入新节点。

template<class ValueType>
struct RBTree
{
	bool Insert(const ValueType& data)
	{
		PNode& pRoot = GetRoot();
		if (nullptr == pRoot)
		{
			pRoot = new Node(data, BLACK);

			// 根的双亲为头节点
			pRoot->_pParent = _pHead;
			_pHead->_pParent = pRoot;
		}
		else
		{
			// 1. 按照二叉搜索的树方式插入新节点
			// 2. 检测新节点插入后,红黑树的性质是否造到破坏,
			//    若满足直接退出,否则对红黑树进行旋转着色处理
		}
		// 根节点的颜色可能被修改,将其改回黑色
		pRoot->_color = BLACK;
		_pHead->_pLeft = LeftMost();
		_pHead->_pRight = RightMost();

		return true;
	}
private:
	PNode& GetRoot()
	{
		return _pHead->_pParent;
	}

	// 获取红黑树中最小节点,即最左侧节点
	PNode LeftMost();

	// 获取红黑树中最大节点,即最右侧节点
	PNode RightMost();

private:
	PNode _pHead;
}

2. 检测新节点插入后,红黑树的性质是否遭到破坏。

因为新节点的默认颜色为红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三,即不能有连在一起的红色节点,此时需要对红黑树分情况来讨论: 

  • 情况一:cur为红,p为红,g为黑,u存在且为红。

注意:此处看到的树可能是一棵完整的树,也可能是一棵子树。

如果g是根节点,调整完成后,需要将g改为黑色。

如果g是子树,g一定有双亲,且g的双亲如果是红色,就需要继续向上调整。 

cur和p均为红,违反了性质三。

解决方法:将p、u改为黑,g改为红,然后把g当成cur,继续向上调整。 

  • 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 说明:

  1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
  2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成了红色。

p为g的左孩子,cur为p的左孩子,则进行右单旋转。

p为g的右孩子,cur为p的右孩子,则进行左单旋转。

p、g变色——p变黑,g变红。

  • 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 

p为g的左孩子,cur为p的右孩子,则针对p进行左单旋转。

p为g的右孩子,cur为p的左孩子,则针对p进行右单旋转。

则转换成情况二。

针对每种情况进行相应的处理即可。

bool Insert(const ValueType& data)
{
	// ...
	// 新节点插入后,如果其双亲节点的颜色为空色,则违反性质3:不能有连在一起的红色结点
		while (pParent && RED == pParent->_color)
		{
			// 注意:grandFather一定存在
			// 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲
			PNode grandFather = pParent->_pParent;
			// 先讨论左侧情况
			if (pParent == grandFather->_pLeft)
			{
				PNode unclue = grandFather->_pRight;
				// 情况三:叔叔节点存在,且为红
				if (unclue && RED == unclue->_color)
				{
					pParent->_color = BLACK;
					unclue->_color = BLACK;
					grandFather->_color = RED;
					pCur = grandFather;
					pParent = pCur->_pParent;
				}
				else
				{
					// 情况五:叔叔节点不存在,或者叔叔节点存在且为黑
					if (pCur == pParent->_pRight)
					{
						_RotateLeft(pParent);
						swap(pParent, pCur);
					}
					// 情况五最后转化成情况四
					grandFather->_color = RED;
					pParent->_color = BLACK;
					_RotateRight(grandFather);
				}
			}
			else
			{
				// …
			}
		}
	// ...
}

1.6 -> 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
  2. 检测其是否满足红黑树的性质。
bool IsValidRBTree()
	{
		PNode pRoot = GetRoot();

		// 空树也是红黑树
		if (nullptr == pRoot)
			return true;

		// 检测根节点是否满足情况
		if (BLACK != pRoot->_color)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;

			return false;
		}

		// 获取任意一条路径中黑色节点的个数
		size_t blackCount = 0;
		PNode pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_color)
				blackCount++;

			pCur = pCur->_pLeft;
		}

		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;

		return _IsValidRBTree(pRoot, k, blackCount);
	}

	bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}

			return true;
		}

		// 统计黑色节点的个数
		if (BLACK == pRoot->_color)
			k++;

		// 检测当前节点与其双亲是否都为红色
		PNode pParent = pRoot->_pParent;
		if (pParent && RED == pParent->_color && RED == pRoot->_color)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;

			return false;
		}

		return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&
			_IsValidRBTree(pRoot->_pRight, k, blackCount);
	}

1.8 -> 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(n),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以在实际运用中红黑树更多。

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

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

  • begin()和end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里呢?能否给成nullptr呢?

答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找到最后一个元素,此处就不行,因此最好的方式是将end()放在头节点的位置

  • operator++()与operator--() 
// 找迭代器的下一个节点,下一个节点肯定比其大
	void Increasement()
	{
		//分两种情况讨论:_pNode的右子树存在和不存在
		// 右子树存在
		if (_pNode->_pRight)
		{
			// 右子树中最小的节点,即右子树中最左侧节点
			_pNode = _pNode->_pRight;
			while (_pNode->_pLeft)
				_pNode = _pNode->_pLeft;
		}
		else
		{
			// 右子树不存在,向上查找,直到_pNode != pParent->right
			PNode pParent = _pNode->_pParent;
			while (pParent->_pRight == _pNode)
			{
				_pNode = pParent;
				pParent = _pNode->_pParent;
			}
			// 特殊情况:根节点没有右子树
			if (_pNode->_pRight != pParent)
				_pNode = pParent;
		}
	}

	// 获取迭代器指向节点的前一个节点
	void Decreasement()
	{
		//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不
		存在
			// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置
			if (_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)
				_pNode = _pNode->_pRight;
			else if (_pNode->_pLeft)
			{
				// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点
				_pNode = _pNode->_pLeft;
				while (_pNode->_pRight)
					_pNode = _pNode->_pRight;
			}
			else
			{
				// _pNode的左子树不存在,只能向上找
				PNode pParent = _pNode->_pParent;
				while (_pNode == pParent->_pLeft)
				{
					_pNode = pParent;
					pParent = _pNode->_pParent;
				}
				_pNode = pParent;
			}
	}

2.2 -> 改造红黑树

#pragma once

// set ->key
// map ->key/value

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)
		, _col(RED)
	{}
};

template<class T>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T> Self;
	Node* _node;

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

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

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

	Self& operator--();

	Self& operator++()
	{
		if (_node->_right)
		{
			// 下一个就是右子树的最左节点
			Node* cur = _node->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}

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

			_node = parent;
		}

		return *this;
	}

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

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

// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, T>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T> iterator;

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

		return iterator(cur);
	}

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

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

		Node* parent = nullptr;
		Node* cur = _root;
		KeyOfT kot;

		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;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				//     g
				//   p   u
				// c
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上更新处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						// 单旋
						//     g
						//   p
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						// 双旋
						//     g
						//   p
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else  // parent == grandfather->_right
			{
				//     g
				//   u   p 
				//          c
				//
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					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), true);
	}

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

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

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

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

			subR->_parent = parentParent;
		}
	}

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

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

		Node* parentParent = parent->_parent;

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

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

			subL->_parent = parentParent;
		}
	}

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

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

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	// 根节点->当前节点这条路径的黑色节点的数量
	bool Check(Node* root, int blacknum, const int refVal)
	{
		if (root == nullptr)
		{
			//cout << balcknum << endl;
			if (blacknum != refVal)
			{
				cout << "存在黑色节点数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

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

			return false;
		}

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

		return Check(root->_left, blacknum, refVal)
			&& Check(root->_right, blacknum, refVal);
	}

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

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

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

			cur = cur->_left;
		}

		int blacknum = 0;
		return Check(_root, blacknum, refVal);
	}

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

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

	size_t Size()
	{
		return _Size(_root);
	}

	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return NULL;
	}

private:
	Node* _root = nullptr;
};

2.3 -> map的模拟实现

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

#pragma once
#include"RBTree.h"

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

		// 对类模板取内嵌类型,加typename告诉编译器这里是类型
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

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

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

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

2.4 -> set的模拟实现

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

#pragma once
#include"RBTree.h"

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

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

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

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

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

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

感谢各位大佬支持!!!

互三啦!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

南京邮电大学运筹学课程实验报告1 线性规划求解 指导

一、题目描述 实验 一 线性规划求解 实验属性&#xff1a; 验证性     实验目的 1&#xff0e;理解线性规划解的基本概念&#xff1b; 2&#xff0e;掌握运筹学软件的使用方法&#xff1b; 3. 掌握线性规划的求解原理和方法。 实验内容 认…

基于Java技术的网上图书商城系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术、SpringBoot框架 工具&#xff1a;Eclipse、Navicat、Maven 系统展示 首页 用户注册界面…

防火墙安全策略与用户认证综合实验

一、实验拓扑 二、实验需求 1.DMZ区内的服务器&#xff0c;办公区仅能在办公时间内<9:00-18:00>可以访问&#xff0c;生产区的设备全天可以访问 2.办公区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网 3.办公区设备10.0.2.10不充许访问DMZ区的FTP服务器和HT…

第58期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

Python程序封装成Windows服务实践

1. WinSW 工具概述 WinSW&#xff08;Windows Service Wrapper&#xff09;是一个轻量级的、开源的工具&#xff0c;用于将任何可执行文件&#xff08;包括exe、jar、脚本文件等&#xff09;包装成一个Windows服务。这意味着&#xff0c;原本设计为命令行应用或需要手动启动的…

03 数据加工层 如何精准匹配用户画像与物品画像?

我们讲解了个性化流量分发体系搭建流程中的数据加工阶段&#xff0c;不过在前面我们主要讲解了如何构建结构化标签&#xff0c;并未提及用户画像和物品画像的构建&#xff0c;以及如何将用户画像与物品画像进行匹配。本节课我们探讨下如何通过深加工帮助用户快速获取有效信息。…

漏洞挖掘思路分享 | 首次尝试cnvd捡洞

因为那天项目刚忙完闲来无事&#xff0c;尝试捡个cnvd洞&#xff0c;cnvd录取要五千万资产&#xff0c;自己又懒得找毕竟捡洞嘛&#xff0c;索性去cnvd上进行搜集 直接开搜弱口令&#xff0c;因为我比较喜欢有登录框的站&#xff0c;这样搜索出来的资产可能就更容易进行挖掘 随…

Paimon下载使用和基础操作说明

简介 Apache Paimon 是一种湖格式&#xff0c;支持使用 Flink 和 Spark 构建实时湖仓一体架构 用于流式处理和批处理操作。Paimon创新性地将湖格式与LSM&#xff08;Log-structured merge-tree&#xff09;相结合 结构&#xff0c;将实时流式更新引入 Lake 架构。 Paimon提供以…

IoC源码分析——singleton bean创建与循环依赖

文章目录 概要主流程bean的创建循环依赖 概要 容器初始化时&#xff0c;会创建单例bean&#xff0c;本文主要关注单例bean是如何创建的&#xff0c;并说明源码中是如何解决循环依赖的 代码入口 Testpublic void testIoC() {// ApplicationContext是容器的高级接口&#xff0c…

中霖教育怎么样?税务专业可以考哪些证书?

在税务专业领域&#xff0c;专业技能的认证对职业发展至关重要。以下为税务专业相关可以考的证书&#xff1a; 1. 注册税务师资格证书&#xff1a;该证书是税务专业人士的关键资质&#xff0c;使持证者可以从事税务相关工作。 2. 会计职称证书&#xff1a;会计系列证书分为初…

即时通讯平台项目测试(主页面)

http://8.130.98.211:8080/login.html项目访问地址&#xff1a;即时通讯平台http://8.130.98.211:8080/login.html 本篇文章进行项目主页面的测试。 在测试前需要先对待测内容进行分类&#xff0c;按照功能进行分类可以分为&#xff1a;个人信息设置、发送/接收消息、添加好友…

python-25-零基础自学python-处理异常三兄弟try-except-else

学习内容&#xff1a;《python编程&#xff1a;从入门到实践》第二版第十章 知识点&#xff1a; 程序异常如何处理&#xff1f;try-except-else try-尝试可能引起错误的步骤 except-错误步骤发生&#xff0c;打印一些需要用户知道的信息&#xff0c;没有就pass else-错误不…

【密码学】公钥密码的基本概念

在先前我写的密码学体制文章中谈到&#xff0c;现代密码学分为两大体制&#xff0c;介绍了一些有关对称密码体制诸如流密码和分组密码的内容。本文的主要内容则切换到公钥密码体制&#xff08;又称非对称密码体制&#xff09;&#xff0c;简述了公钥密码体制的基本思想和应用方…

MFC常见问题解决

文章目录 1. 单文档程序初始化显示设置问题解决方案 2. MFC中控件响应出错 1. 单文档程序初始化显示设置 问题 在Microsoft Foundation Classes (MFC) 中&#xff0c;单文档应用程序&#xff08;SDI&#xff09;的初始化时默认并不设置为最大显示。但你可以通过编程方式在程序…

传神论文中心|第16期人工智能领域论文推荐

在人工智能领域的快速发展中&#xff0c;我们不断看到令人振奋的技术进步和创新。近期&#xff0c;开放传神&#xff08;OpenCSG&#xff09;社区发现了一些值得关注的成就。传神社区本周也为对AI和大模型感兴趣的读者们提供了一些值得一读的研究工作的简要概述以及它们各自的论…

MiniGPT-Med 通用医学视觉大模型:生成医学报告 + 视觉问答 + 医学疾病识别

MiniGPT-Med 通用医学视觉大模型&#xff1a;生成医学报告 视觉问答 医学疾病识别 提出背景解法拆解 论文&#xff1a;https://arxiv.org/pdf/2407.04106 代码&#xff1a;https://github.com/Vision-CAIR/MiniGPT-Med 提出背景 近年来&#xff0c;人工智能&#xff08;AI…

Mybatis的学习

什么是MyBatis? MyBatis 是一款优秀的持久层框架&#xff0c;用于简化JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code&#xff0c;并且改名为MyBatis。2013年11月迁移到Github 官网:https://mybatis…

Python的语言特性

1&#xff0c;python是动态语言 在编译期间就确定变量类型的语言是静态语言 在运行期间才知道变量类型的是动态语言 2&#xff0c;python是强类型语言 不同类型的变量是否允许隐式转换

简过网:哪些证书适合上班族考?这些证书挂着就能挣钱!

考证不一定暴富&#xff0c;但找工作时一定吃香 今天给大家整理几个比较吃香的证书&#xff01; 不仅含金量高还可以领补贴 建议大家有时间都考一考 普通人就冲一冲&#xff1a;教资、二建、一建、一造、监理&#xff01;&#xff01; ​ ★ 教师资格证书 报名时间&#x…

STM32G4 DMA的使用(寄存器开发)

下面以STM32G474为例&#xff0c;使用DMA来存储USART1的接收数据。 1. 查看硬件支持 首先查看要使用的DMA支持的通道数&#xff0c;在手册中有如下说明。 根据上图可以看到&#xff0c;对于不同的设备类型有不同的DMA通道数量。设备类型分类如下图所示。 我使用的是STM32G474…