【C++ STL】-- 红黑树的插入实现

news2024/12/23 16:19:12

目录

红黑树的概念

二叉树搜索树的应用

红黑树节点的定义

红黑树结构

insert

需调整的多情况的核心思维:

需调整的多情况分类讲解:

情况一:

情况二:

情况三:

总结:

代码实现:

对于红黑树是否建立成功的检查

升序打印

检查是否为红黑树

执行

代码汇总


红黑树的概念

        红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的:
  • 每个结点不是红色就是黑色
  • 根节点是黑色的 
  • 如果一个节点是红色的,则它的两个孩子结点是黑色的 
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

        所以通过两个颜色,以红色不能相挨为核心,打造出一条路径最多为黑-红-黑-红,叶节点NIL为黑且是空。而另一条路径为全黑,这样正好是,满足上面的性质,且红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍。

二叉树搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树。
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英

  • 文单词与其对应的中文<word, chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
  • 使用一个结构体pair:

pair的相关文件

红黑树节点的定义

        对于红黑树的实现,以K模型做讲解。

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

// 红黑树节点的定义
template<class ValueType>
class RBTreeNode {
	RBTreeNode<ValueType>* _Right;  // 节点的右孩子
	RBTreeNode<ValueType>* _Left;   // 节点的左孩子
	RBTreeNode<ValueType>* _Parent; // 节点的双亲

	ValueType _Data; // 节点的值
	Color _Color;

	// 构造函数
	RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
		:_Right(nullptr), _Left(nullptr), _Parent(nullptr)
		,_Data(data), _Color(color)
	{}
};

红黑树结构

        首先,由于红黑树符合其最长路径中黑色节点个数不会超过最短路径黑色节点个数的两倍。那么对于一个数据的插入,就会考虑为红色的,因为插入黑色面临黑色节点的个数改变,调整更难,于是考虑简单的红色,因为其不会考虑个数,自会考虑是否由两个红节点相连。

        (由于红黑树的模型主要关注的是节点颜色,所以篇博客的图以三角形代表抽象的红黑树结构)

insert

        对于insert最友好的情况就是插入节点的父节点就是黑色、插入节点就是根节点,对于节点的父节点就是黑色,且并未干扰到路径的黑色节点个数的同时,也不会有两个红色节点相挨:

        插入节点就是根节点:

// 插入的位置是根节点
if (_root == nullptr)
{
	_root = new Node(data);
	_root->_color = BLACK;
	return true;
}

需调整的多情况的核心思维:

  • cur(插入)为红,parent为红,grandfather为黑

         由于我们插入的颜色为红色,所以主要的调整就是颜色调整,而我们插入的环境是红黑树之下,所以处于颜色的不平衡只会是:插入的节点为红,其父节点为红,父节点的父节点为黑,这个是一定的。

(因为颜色不成立一定是两个红色相挨了,而相挨的只能是插入的与其的父节点,否则原来必定不是红黑树结构)

  • insert的多情况由于uncle(父节点的兄弟节点)

        我们的插入的节点的干扰到的颜色分布一定是grandfather节点的子树的黑节点个数平衡,而又影响到grandfather节点的parent节点的左右子树,以此向上,一直到根节点,最终就是一节颗树都不符合。所以重点就是左右子树,也就是parent与uncle节点颜色的问题。

        而如果uncle为红还好,可以直接与father一起变黑色保持左右黑节点个数的同时解决掉cur(插入)为红,parent为红的情况,随后grandfather节点变为新的插入(具体实现后面细讲)。如此渐渐向上,保持左右子树黑节点个数平衡颜色达标。此外,如果uncle不在呢?uncle为黑色呢,所以,这就是多情况的原因。

需调整的多情况分类讲解:

  • 情况一:

    • cur为红,p为红,g为黑,u存在且为红

          由于a、b、c、d、e的情况有很多很多,所以我们就以简易的具象图结构讲解,原理与复杂结构是一样的:

一.  a、c、d、e是NIL

二. a、b、c、d、e是最简单的子树结构

        对此由于插入的节点一定是叶子节点,所以不可能由插入在中间的情况,需要分析:

        所以,实际上对于此种情况转换过来时的情况为:

         随后:

情况一总结:

  • 固定情况:
    • u存在且为红
  • 固定处理:
    • p、u变黑、g变红。继续把g当成cur,g不是根继续往上处理,g是根在其变为红色的情况下变为黑色。

  • 情况二:

    • cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋)

        由于u不存在/u存在且为黑可以更加详细的画出:

1. u存在且为黑的情况具像图举例:

        cur其实原来是黑色的,是由情况一变化而来的。


处理的核心:

        因为红黑树是是一个标准的搜索树,所以一定符合左子树 < 根 < 右子树,那么对于路径长度的变换,是不能通过简单的平移节点所能达成的:

左旋:

        根 < 左子树是关键。

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

	// 将subR连接到parent的右
	parent->_right = subRL;
	if (subRL) // 可能subR的左子树不存在
		subRL->_parent = parent;

	Node* pparent = parent->_parent;

	// 将parent连接到subR的左节点上,成为subR的左子树
	subR->_left = parent;
	parent->_parent = subR;

	// 将parent的父节点状态给予subR
	if (_root == parent) // parent是根
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else // parent不是根
	{
		if (pparent->_left == parent)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
}

右旋:

        根 > 右子树是关键。

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

	// 将subL连接到parent的左
	parent->_left = subLR;
	if (subLR) // 可能subL的右子树不存在
		subLR->_parent = parent;

	Node* pparent = parent->_parent;

	// 将parent连接到subL的右节点上,成为subL的右子树
	subL->_right = parent;
	parent->_parent = subL;

	// 将parent的父节点状态给予subL
	if (_root == parent) // parent是根
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else // parent不是根
	{
		if (pparent->_left == parent)
			pparent->_left = subL;
		else
			pparent->_right = subL;
		subL->_parent = pparent;
	}
}

对于u不存在的处理方式(右单旋):

        以g为轴点右单旋。然后p变黑、g变红。

对于u存在且为黑的处理方式(右单旋):

        与u不存在时同理,以g为轴点右单旋。然后p变黑、g变红。


        上述是右单旋转的情况,如果p和u的位置换了换,就是左单旋然后p变黑、g变红。

对于u不存在的处理方式(左单旋):

        以g为轴点左单旋。然后p变黑、g变红。

对于u存在且为黑的处理方式(左单旋):

       与u不存在时同理,以g为轴点左单旋。然后p变黑、g变红。

情况二总结:

  • 固定情况:
    • u不存在/u存在且为黑
  • 固定处理:(单旋变色)
    • 单选后,p变黑、g变红
      • a、 p 为 g 的左孩子,cur 为 p 的左孩子,则进行右单旋转p变黑、g变红
        b、 p 为 g 的右孩子,cur 为 p 的右孩子,则进行左单旋转,p变黑、g变红

  • 情况三:

    • cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋)

         由于u不存在/u存在且为黑可以更加详细的画出:

 1. u存在且为黑的情况具像图举例:

        cur其实原来是黑色的,是由情况一变化而来的。


对于u不存在的处理方式(右单旋):

        以p为轴点右单旋。则转换成了情况二。

对于u存在且为黑的处理方式(右单旋):

        与u不存在时同理,以p为轴点右单旋。则转换成了情况二。

对于u不存在的处理方式(左单旋):

        以p为轴点左单旋。则转换成了情况二。

 对于u存在且为黑的处理方式(左单旋):

        与u不存在时同理,以p为轴点左单旋。则转换成了情况二。

情况三总结:

  • 固定情况:
    • u不存在/u存在且为黑
  • 固定处理:(单旋变色)
    • 单选后,则转换成了情况二
      • a、 p g 的左孩子, cur p 的右孩子,则针对 p做左单旋转,则转换成了情况二
        b、 p g 的右孩子, cur p 的左孩子,则针对 p做右单旋转。则转换成了情况

总结:

Note:红黑树的关键是叔叔节点

  • u存在且为红,变色继续往上处理。
  • u不存在或存在且为黑,旋转 + 变色
    • 单旋 + 变色
    • 双旋 + 变色

代码实现:

核心实现:

        对于情况一:问题不大,因为没有左右旋。所以,对于parent处于grandfather左右节点位置,只会是uncle与father,处于位置的变化。

        对于情况二、三:uncle与father,处于位置的须注意的同时。又由于需要左右旋,所以,对于parent处于grandfather左右节点位置很重要的,那么就可以以(情况二:单旋+变色     情况三:单旋+情况二)的第一个操作分为一起

bool insert(const ValueType& data)
{
	// 插入的位置是根节点
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_color = BLACK;
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (data < cur->_data)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
			return false;
	}

	cur = new Node(data);
	cur->_color = RED;

	if (cur->_data > parent->_data)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	while (parent && parent->_color == RED)
	{
		Node* grandfather = parent->_parent;
		assert(grandfather);
		assert(grandfather->_color == BLACK);
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;

			//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
			if (uncle && uncle->_color == RED)
			{
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;
				//继续向上
				cur = grandfather;
				parent = cur->_parent;
			}
			else// 情况二+三:uncle不存在 + 存在且为黑
			{
				//情况二:右旋 + p变黑,g变红
				//      g
				//   p     u
				// c
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
				//     g
				//  p     u
				//    c
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;

			//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
			if (uncle && uncle->_color == RED)
			{
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;
				//继续向上
				cur = grandfather;
				parent = cur->_parent;
			}
			else// 情况二+三:uncle不存在 + 存在且为黑
			{
				//情况二:左旋 + p变黑,g变红
				//      g
				//   u     p
				//           c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
				//      g
				//   u     p
				//       c
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				break;
			}
		}
	}
	_root->_color = BLACK;
	return true;
}

对于红黑树是否建立成功的检查

升序打印

        利用前序递归实现,升序打印,因为this指针没法递归,所以需写一个函数传入根节点。

// 利用递归前序按升序打印红黑树
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

// 前序递归
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_data << " ";
	_InOrder(root->_right);
}

检查是否为红黑树

  • 是红黑树的重点:
    • 颜色分布(无红色节点相连)
    • 每条路径的黑色节点个数相同
    • 最长路径最多为最短路径的2倍
    • 根节点存在即为黑色

        对于根节点存在即为黑色颜色分布(无红色节点相连),第一个:if判断即可,第二个:节点与其的父节点不同时为红色即可。

        对于最长路径最多为最短路径的2倍每条路径的黑色节点个数相同。如果从 " 最长路径最多为最短路径的2倍入手 " ,即以高度入手,是可以保证最长路径最多为最短路径的2倍,但是是没有办法保证颜色的分布,而,如果以 " 每条路径的黑色节点个数相同 " 入手,利用无红色节点相连就可以保证,在每条路径的黑色节点个数相同的前提下,最长路径最多为最短路径的2倍。

// 检测是否符合红黑树
bool IsBalance()
{
	if (_root == nullptr)
	{
		return true;
	}

	// 判断根节点是否为黑色
	if (_root->_color == RED)
	{
		cout << "根节点不黑色" << endl;
		return false;
	}

	int benchMark = 0; //某路径的黑节点个数,作为基准值
	return PrevCheck(_root, 0, benchMark);
}

// 利用深度优先
bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
{
	if (root == nullptr)
	{
		if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
			benchMark = blackNum;
		else if (blackNum != benchMark)
			return false;
		return true;
	}

	if (root->_color == BLACK)
		++blackNum;

	// 检查是否有连续的红色节点
	if (root->_color == RED && root->_parent->_color == RED)
	{
		cout << "存在连续的红节点" << endl;
		return false;
	}

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

执行

void TestRBTree()
{
	size_t N = 100;
	srand(time(0));
	RBTree<int> t;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		t.insert(x);
	}

	t.InOrder();

	if (t.IsBalance())
		cout << "是红黑树" << endl;
	else
		cout << "不是红黑树" << endl;
}

int main()
{
	TestRBTree();
	return 0;
}

代码汇总

#include<iostream>
#include<assert.h>
using namespace std;
// 节点颜色
enum Color{RED, BLACK};

// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode {
	RBTreeNode<ValueType>* _right;  // 节点的右孩子
	RBTreeNode<ValueType>* _left;   // 节点的左孩子
	RBTreeNode<ValueType>* _parent; // 节点的双亲

	ValueType _data; // 节点的值
	Color _color;

	// 构造函数
	RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
		:_right(nullptr), _left(nullptr), _parent(nullptr)
		,_data(data), _color(color)
	{}
};

template<class ValueType>
class RBTree {
	typedef RBTreeNode<ValueType> Node;
public:
	bool insert(const ValueType& data)
	{
		// 插入的位置是根节点
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
        // 查找cur插入的位置
		while (cur)
		{
			if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
				return false;
		}

		cur = new Node(data);
		cur->_color = RED;

		if (cur->_data > parent->_data)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_color == BLACK);
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				
				//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					//继续向上
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 情况二+三:uncle不存在 + 存在且为黑
				{
					//情况二:右旋 + p变黑,g变红
					//      g
					//   p     u
					// c
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
				    //     g
				    //  p     u
				    //    c
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;

				//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					//继续向上
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 情况二+三:uncle不存在 + 存在且为黑
				{
					//情况二:左旋 + p变黑,g变红
					//      g
					//   u     p
					//           c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
					//      g
					//   u     p
					//       c
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return true;
	}

	// 利用递归前序按升序打印红黑树
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	// 检测是否符合红黑树
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		// 判断根节点是否为黑色
		if (_root->_color == RED)
		{
			cout << "根节点不黑色" << endl;
			return false;
		}

		int benchMark = 0; //某路径的黑节点个数,作为基准值
		return PrevCheck(_root, 0, benchMark);
	}

private:
	// 利用深度优先
	bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
	{
		if (root == nullptr)
		{
			if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
				benchMark = blackNum;
			else if(blackNum != benchMark)
				return false;
			return true;
		}

		if (root->_color == BLACK)
			++blackNum;

		// 检查是否有连续的红色节点
		if (root->_color == RED && root->_parent->_color == 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->_data << " ";
		_InOrder(root->_right);
	}

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

		// 将subL连接到parent的左
		parent->_left = subLR;
		if (subLR) // 可能subL的右子树不存在
			subLR->_parent = parent;

		Node* pparent = parent->_parent;

		// 将parent连接到subL的右节点上,成为subL的右子树
		subL->_right = parent;
		parent->_parent = subL;

		// 将parent的父节点状态给予subL
		if (_root == parent) // parent是根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else // parent不是根
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
	}

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

		// 将subR连接到parent的右
		parent->_right = subRL;
		if (subRL) // 可能subR的左子树不存在
			subRL->_parent = parent;

		Node* pparent = parent->_parent;

		// 将parent连接到subR的左节点上,成为subR的左子树
		subR->_left = parent;
		parent->_parent = subR;

		// 将parent的父节点状态给予subR
		if (_root == parent) // parent是根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else // parent不是根
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
	}

	Node* _root = nullptr;
};

void TestRBTree()
{
	size_t N = 100;
	srand(time(0));
	RBTree<int> t;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		t.insert(x);
	}

	t.InOrder();

	if (t.IsBalance())
		cout << "是红黑树" << endl;
	else
		cout << "不是红黑树" << endl;
}

int main()
{
	TestRBTree();
	return 0;
}

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

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

相关文章

C++--类型转换--1128

1.C语言中的类型转换 分为隐式类型转化、显示强制类型转化。 隐式类型转化用于意义相近的类型&#xff0c;比如int,double,short都是表示数值的类型 int i1; double di; //编译、结果无问题 这里是隐式类型转换。 显示强制类型转换 显示强制类型用于意义不相近的类型&…

Redis Sentinel

高可用架构-Redis Sentinel Replication 缺点 接着之前的Redis Replication 主从复制架构&#xff0c;看似解决了主节点并发过大时&#xff0c;master节点处理繁忙的问题。将一部分读数据的请求交给从节点处理&#xff0c;从而将请求进行分散处理。但是该架构却存在很明显的缺…

基于LEACH的随机网络生成无线传感器网络路由协议的仿真比较(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

【深度学习】超详细的 PyTorch 学习笔记(上)

文章目录一、PyTorch环境检查二、查看张量类型三、查看张量尺寸和所占内存大小四、创建张量4.1 创建值全为1的张量4.2 创建值全为0的张量4.3 创建值全为指定值的张量4.4 通过 list 创建张量4.5 通过 ndarray 创建张量4.6 创建指定范围和间距的有序张量4.7 创建单位矩阵&#xf…

【力扣算法简单五十题】23.环形链表

给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;索…

基于多种优化算法及神经网络的光伏系统控制(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;本科计算机专业&#xff0c;研究生电气学硕…

NNDL 实验八 网络优化与正则化(3)不同优化算法比较

文章目录7.3 不同优化算法的比较分析7.3.1 优化算法的实验设定7.3.1.1 2D可视化实验7.3.1.2 简单拟合实验7.3.1.3 与Torch API对比&#xff0c;验证正确性7.3.2 学习率调整7.3.2.1 AdaGrad算法7.3.2.2 RMSprop算法7.3.3 梯度估计修正7.3.3.1 动量法7.3.3.2 Adam算法7.3.4 不同优…

【并发】深度解析CAS原理与底层源码

【并发】深度解析CAS原理与底层源码 什么是 CAS&#xff1f; CAS全称是&#xff08;Compare And Swap&#xff0c;比较并交换&#xff09;&#xff0c;通常指的是这样一种原子操作&#xff08;针对一个变量&#xff0c;首先比较它的内存值与某个期望值是否相同&#xff0c;如…

不就是Redis吗?竟让我一个月拿了8个offer,其中两家都是一线大厂

在高并发的场景Redis是必须的&#xff0c;而 Redis非关系型内存存储不可谓不彪悍。 支持异步持久化达到容灾&#xff1a;速度快、并发高。官方号称支持并发11万读操作&#xff0c;并发8万写操作。惊了吗&#xff1f; 支持数据结构丰富&#xff1a;string&#xff08;字符串&a…

盘点5种最频繁使用的检测异常值的方法(附Python代码)

本文介绍了数据科学家必备的五种检测异常值的方法。 无论是通过识别错误还是主动预防&#xff0c;检测异常值对任何业务都是重要的。本文将讨论五种检测异常值的方法。 文章目录什么是异常值&#xff1f;为什么我们要关注异常值&#xff1f;技术提升方法1——标准差方法2——箱…

【OpenEnergyMonitor】开源的能源监控系统--项目介绍

OpenEnergyMonitor1. 系统框架2.项目组成2.1 emonPi模块:2.1.1 emonpi的安装&#xff1a;2.1.2 emonTx & emonBase 安装2.1.3 emonTx Wifi 安装&#xff1a;2.1.4 添加额外的 emonTx 节点&#xff1a;2.1.5 添加额外的emonTx-节点监控三项电源2.1.6 添加 emonTH 温度节点2.…

【Vue核心】8.计算属性

1. 定义: 要用的属性不存在,要通过已有属性计算得来。 2. 原理 底层借助了objcet.defineproperty方法提供的getter fllsetter. 3. get两数什么时候执行? (1),初次读取时会执行一次。 (2),当依赖的数据发生改变时会被再次调用。 4. 优势 与methods实现相比,内部有缓存机…

进厂手册:Git 学习笔记(详解命令)

文章目录git 对象通过git对象进行文件的保存git对象的缺点树对象构建树对象提交对象高层命令工作区的文件状态git reset hard 咋用以及用错了怎么恢复git checkout vs git resetGit存储后悔药工作区暂存区版本库reset三部曲checkout深入理解tag远程上的相关操作ssh登入一些个人…

[附源码]计算机毕业设计家庭整理服务管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

LeetCode刷题复盘笔记—一文搞懂动态规划之213. 打家劫舍 II问题(动态规划系列第十八篇)

今日主要总结一下动态规划完全背包的一道题目&#xff0c;213. 打家劫舍 II 题目&#xff1a;213. 打家劫舍 II Leetcode题目地址 题目描述&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一…

快速排序详解

快速排序&#xff0c;简称快排。其实看快速排序的名字就知道它肯定是一个很牛的排序&#xff0c;C语言中的qsort和C中的sort底层都是快排。 快速排序由于排序效率在同为O(N*logN)的几种排序方法中效率较高&#xff0c;因此经常被采用&#xff0c;再加上快速排序思想----分治法…

Opencv 基本操作五 各种连通域处理方法

在深度学习中&#xff0c;尤其是语义分割模型部署的结果后处理中&#xff0c;离不开各类形态学处理方法&#xff0c;其中以连通域处理为主&#xff1b;同时在一些传统的图像处理算法中&#xff0c;也需要一些形态学、连通域处理方法。为此&#xff0c;整理了一些常用的连通域处…

leetcode每日一题寒假版:1691. 堆叠长方体的最大高度 (hard)( 换了皮的最长递增子序列)

2022-12-10 1691. 堆叠长方体的最大高度 (hard) &#x1f6a9; 学如逆水行舟&#xff0c;不进则退。 —— 《增广贤文》 题目描述&#xff1a; 给你 n 个长方体 cuboids &#xff0c;其中第 i 个长方体的长宽高表示为 cuboids[i] [width(i), length(i), height(i)]&#xf…

Docker补充知识点--自定义网络实现直连容器

前面介绍docker镜像的秘密这篇知识点的时候&#xff0c;https://blog.csdn.net/dudadudadd/article/details/128200522&#xff0c;提到了docker容器也有属于自己的IP的概念&#xff0c;默认的Docker容器是采用的是bridge网络模式。并且提到了一嘴自定义网卡配置&#xff0c;本…

java基于Springboot的健身房课程预约平台-计算机毕业设计

项目介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven 本健身网站系统是针…