c++ 红黑树学习及简单实现

1. 了解红黑树

1.1. 概念

红黑树,是一种二叉搜索树,但在每个节点增加一个存储位表示节点的颜色,可以是红色,或是黑色,通过对任何一条从根到叶子的路径上各个节点的着色方式进行限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡。
都是搜索二叉树,红黑树和AVL树有什么区别?
AVL 树:左右高度差不超过1(严格平衡)
红黑树:最长路径不超过最短路径的两倍(近似平衡)
这样看起来,似乎 AVL树更优秀,严格平衡
如果相同的数据,AVL 树可能是 18层,但是 红黑树有可能是 18 - 36 层
在查找方面 AVL 树可能会比红黑树优秀一点,但是只是一点。
因为18 层的数据,2^17 ==131072,18层数据就有 这么多节点,查找 18 次和 查找 35次,对计算机来说区别很小。
同时还要注意,AVL树的严格平衡,因为严格平衡,所以 AVL树为了保证平衡,需要不停的旋转,因此在插入删除上,效率会慢很多
但是红黑树并不是严格平衡,所以在调整上,不会进行那么多次的调整,因此插入和删除的时间就会少很多。

1.2. 性质

先看下面一棵树
在这里插入图片描述
红黑树性质:

  1. 每个节点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个节点都是黑色的(不能出现连续的红色节点,可以出现的组合:黑+黑, 黑+红, 红+黑)。
  4. 对于每个节点,从该节点到其所有后代中,均包含数目相同的黑色节点(每条路径都包含相同数量的黑色节点)。
  5. 每个叶子节点(NIL节点)都是黑色的。

为什么满足上面的性质,红黑树就能保证:最长路径中节点个数不会超过最短路径节点个数的两倍?
最短路径:全黑
最长路径:一黑一红间隔
在这里插入图片描述
假设最短上有 N 个节点
其他路径上的节点数量都在 [N, 2N] 之间
每条路径上黑色节点数量相同,最长的情况下是一红一黑间隔,同一条路径下,红节点数量和黑节点数量相同,也就是2
N
NIL 节点(叶子节点的空节点)
在这里插入图片描述
比如这棵树,这里看起来有 4 条理解,其实有 8 条
NIL 节点也要被算入路径内
路径是从跟节点走到空,才算路径。
在这里插入图片描述
这棵树也算红黑树,这棵树有 7 条路径。
在这里插入图片描述
这棵树,我们看起来好像是一棵红黑树,但是当我们把所有的 NIL 节点全部标出来
在这里插入图片描述

这样就能清楚的看到,标记位置的右子树只含有一个黑色节点,但是左子树的每条路径含有 两个黑色节点,所以 这棵树并不是红黑树

2. 模拟实现

2.1. 节点

enum Colour
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	pair<K, V> _kv;
	Colour _col;
	
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
	{}
};

这里的节点和 AVL 树类似,不过 红黑树 是用颜色来控制的,所以这里我们添加了 _col 来控制。
不够在这里的 构造函数,我们还不能确定 _col 初始化成什么样子,我们后面再看。

2.2. insert

首先 红黑树 还是搜索二叉树,所以插入的基本逻辑还是那套,我们先实现这部分,后面主要分析调整的地方。

template<class k, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V> Node;
	RBTree()
		:_root(nullptr)
	{}
	bool insert(const pair<K, V>* kv)
	{
		if(_root == nullptr)
		{
			_root = new Node;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while(cur)
		{
			if(cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(cur);
		if(parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		return true;
	}
private:
	Node* _root;
};

二叉树的插入,这段没什么好说的,接下来看调整。
如果是插入节点,最开始应该插入什么颜色的节点最好?
我们都分析一下
在这里插入图片描述
如果我们在上面这棵树上插入节点,插入黑色节点后,我们发现,这条路径上的黑色节点数量发生变化,所以这里必须调整。
如果插入的是红色节点,每条路径上的黑色节点数量并未发生变化,这里可以不需要调整。
所以我们最好把 插入的节点初始化为 红色节点
在这里插入图片描述
在这里插入图片描述
但这不代表插入红色节点完全不需要修改,插入红色节点后,主要影响的是父节点。
这里插入分为 3 类型的情况:

  1. 如果新增的节点是 黑色节点,会影响所有父亲节点下面的路径。
  2. 如果新增的节点是 红色节点,父亲节点是 黑色节点,不用影响。
  3. 如果新增的节点是 红色节点, 父亲节点是 红色节点, 需要进行调整。

我们需要处理的情况是第 3 种。
在这里插入图片描述
如果出现这种情况,我们首先考虑把父亲变色。
如果仅靠变色不能解决问题,就需要通过 旋转 + 变色。
在这里插入图片描述

第一步,7 变 黑,因为 7 的父节点一定是 黑色,所以还要处理 父节点(7原本是红色,红色节点的父亲只能是黑色,7 所在的路径上多了一个黑色节点,所以还是要从 7 的父亲节点入手处理)。
第二步,7 的 叔叔节点 5 变 黑,但是又会导致 6 的两条路径上分别多出来一个 黑色节点,所以我们还需要把 6 变 红,从而使每条路径黑色节点数量想同。
这里调整自己路径后,还要通过自己的根去调整另一条路径,我们这里默认的情况是 叔叔节点 存在 且 和 7 都是红色。
那么叔叔不存在的情况,或者叔叔颜色原本为黑呢?
叔叔 不存在时:
在这里插入图片描述
这两棵树,如果是前面学过 AVL 树的人,应该能一眼看出来,这里是 右左双旋 和 右右左单旋。
在这里插入图片描述
旋转 + 变色 处理。
当然,这是基于 叔叔节点不存在的情况,叔叔节点存在且为黑的情况还要讨论,现在我们慢慢分析怎么用代码实现这些操作

2.2.1. 调整

我们先把需要调整的情况主要分为三种
在这里插入图片描述先确定节点
插入的节点: cur
插入节点的父节点: parent - p
插入节点的父节点的父节点: grandfather - g
叔叔节点: uncle - u
a,b,c,d,e 子树(可能为空,也可能是一整颗树)

  1. cur 为红,p 为红,g 为黑, u存在且为红
  2. cur 为红,p为红, g 为黑, u不存在
  3. cur 为红,p为红, g 为黑, u存在且为黑

首先是 第一种 情况:=
cur 为红,p 为红,g 为黑, u存在且为红
解决方法:将 p ,u 改为黑色, g 改为红色, 然后把 g 当做 cur,继续向上处理
在这里插入图片描述
但是简简单单变个色就行了吗?
这里还是有特殊情况需要处理的

  1. 如果 g 是 根节点
  2. 如果 g 的父节点是 红色
    在这里插入图片描述

第一种情况很简单,直接把 _root 变黑色就行了。
毕竟不能出现连续的红色节点,但是可以出现连续的黑色节点,所以这样变完全没有问题
那第二种呢,修改后,g 变成了 红色,但是如果 g 是一棵树的子树,同时 g 的父亲节点是 红色呢?
我们的子树是没有问题了,所以我们把 原树的 g 当做新的 cur 向上调整。
在这里插入图片描述
如果 修改后的树 的父亲节点是黑色,那么此时直接退出即可(原本g为根节点的树每条路径是 一个 黑色节点,修改后每条路径仍是一个黑色节点,对 g 以上的根节点来说,每天路径上的黑色节点数量没有发生改变,所以不需要向上调整)
这里我们像 AVL 树那样分析分析,是不是 只需要改变这 4 个节点才能完成操作?
在这里插入图片描述
还是这棵树,此时 a,b,c,d,e 的情况有点复杂
如果 a/b/c/d/e 都是 空,cur 就是新增节点。
如果 a/b 不是空节点,但是想要触发这个调整,必须在 a,b任意位置插入节点就会破坏规则
至于 c,d,e 可能是下面的任意一种情况
在这里插入图片描述
c,d,e 是每条路径上含有一个黑色节点的红黑树。
a,b 作为之前新插入的节点。
在 a,b 下任意位置插入节点都会破坏规则。
所以这里可能存在的树 (4x4x4)x4 = 256种
不管再怎么复杂,对应的子树都是经过调整后的 红黑树,或者说下面调整玩完成后向上调整到这个位置,所以我们只需要关注这里的 cur 就行。

第二种情况:
cur 为红,p为红, g 为黑, u不存在
在这里插入图片描述
如果遇到单纯变色不能解决问题的情况,这里就需要先旋转后变色。
解决方法:左左右单旋,旋转后,g变红, p变黑
这里一般没有什么特殊情况,最后根节点也处理成黑色,不会和父节点冲突
第三种情况:
cur 为红,p为红, g 为黑, u为黑
在这里插入图片描述
这种情况会出现吗,这里 u 可能是上一次调整后的结构,g 可能是根节点,所以会出现这种情况。
这里的处理方法,还是先进行旋转。
但是还是要看 cur 的位置
p 为 g 的左孩子,cur 为 p 的左孩子,进行右单旋
p 为 g 的右孩子,cur 为 p 的右孩子,进行左单旋
p 为 g 的左孩子,cur 为 p 的右孩子,进行左右双旋
p 为 g 的右孩子,cur 为 p 的左孩子,进行右左双旋
在这里插入图片描述

这里默认 a,b,c 都是含有一个黑色节点的红黑树
图不好画,大概按这样的过程理解
旋转完成后,还要变色,主要的变色逻辑就是上面展示的。
单旋:p 变为 黑,g 变为 红色
双旋:g 变为红, cur 变为黑
旋转的大概逻辑了解以后,下面开始实现

while(parent && parent->_parent && parent->_col == RED)
{
	Node* grandfather = parent->_parent;
	if(parent == grandfather->_left)
	{
		Node* uncle = grandfather->_right;
		if(uncle && uncle->_col == RED)
		{
			uncle->_col = parent->_col = BLACK;
			grandfather->_col = RED;
			
			cur = grandfather;
			parent = cur->_parent;
		}
		else
		{
			if(cur == parent->_left)
			{
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				RotateL(parent);
				RotateR(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
	else
	{
		Node* uncle = grandfather->_left;
		if(uncle && uncle->_col == RED)
		{
			uncle->_col = parent->_col = BLACK;
			grandfather-_col = RED;
			cur = grandfather;
			parent = cur->_parent;
		}
		else
		{
			if(cur == parent->_right)
			{
				RotateL(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				RotateR(parent);
				RotateL(grandfather);
				cur->_col = BLACK;
				grandfather->_col =RED;
			}
		}
	}
	_root->_col = BLACK;
}

左旋和右旋还是 AVL 树那套,但是要注意,这里我们在 insert 里修改节点颜色,所以左旋右旋里不需要修改颜色,同时 也不需要我们去单独写函数实现 左右双旋和右左双旋来修改颜色。

   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 (parent == _root)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (parentParent->_left == parent)
            {
                parentParent->_left = subL;
            }
            else
            {
                parentParent->_right = subL;
            }
            subL->_parent = parentParent;
        }
    }

2.2.2. 插入全代码

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_col = RED;

		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

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

					cur = grandfather;
					parent = cur->_parent;
				}
                else
                {
                    if (cur == parent->_left)
                    {
                        RotateR(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    else
                    {
                        RotateL(parent);
                        RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
                    }
                    break;
                }
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;

		return 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 (parent == _root)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (parentParent->_left == parent)
            {
                parentParent->_left = subL;
            }
            else
            {
                parentParent->_right = subL;
            }
            subL->_parent = parentParent;
        }
    }

2.3. check

现在插入的大概逻辑是没问题了,但是和 AVL 树的问题一样,怎么样证明我们的树是 红黑树?
树是不是红黑树,我们需要根据红黑树的性质来判断
在这里插入图片描述
第 1 点,第 2 点,第 5 点都很好检查。
第 3 点相对来说就麻烦了
如果是红色节点,检查子节点是不是都是黑的,但是也不好检查,红色节点的孩子节点可能是 两个,可能是 一个,也可能不存在,所以我们可以考虑不向下查找,向上查找

bool Check(Node* root)
{
	if(root == nullptr)
	{
		return true;
	}
	if(root->_col == RED)
	{
		
	}
	return Check(root->_left) && Check(root->_right);
}
bool IsBalance(Node* root)
{
	if(root->_col ==BLACK)
	{
		return true;
	}
	if(root->_col == RED)
	{
		return false;
	}
	return Check(root);
}

IsBalance 检查根节点是不是黑色,Check 检查每个红色节点是不是有两个黑色子节点,如果直接检查红色节点的子节点情况,有点麻烦,所以我们反向检查

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

这里的判断条件可以这样写,当前节点为红色,且这个节点的父节点是红色,就返回 false;
红色节点一定有父亲,且红色节点的父亲一定是黑色。
接下来处理,每条路径上的黑色节点数量相同,该怎么操作?
初步想法,拿栈可以检查,黑色节点入栈,当需要出栈时检测一下数量即可。
除了这个方法,我们还有其他方法
isBalance 传入 blacknum ,当遇见黑色节点++,直到遇见空节点时输出 blacknum 。

bool Check(Node* root, int blacknum)
{
	if(root == nullptr)
	{
		cout << blacknum << "  ";
	}
	if(root->_col == RED & root->_parent->_col == RED)
	{
		cout << "有连续的红色节点" << endl;
	}
	if(root->_col == BLACK)
	{
		++blacknum;
	}
	return Check(root->_left, blacknum) && Check(root->_right, blacknum);
}
bool IsBalance()
{
	return _IsBalance(_root);
}
private:
bool _IsBalance(Node* root)
{
	if(root->_col == RED)
	{
		return false;
	}
	int blacknum = 0;
	return Check(root, blacknum);
}

在这里插入图片描述
这里我们看见,所有路径的黑色节点数量都是 2
但是数据量太少了,我们需要和 AVL 树一样,传入大量数据来测试我们的 红黑树

void test_RBT ree2()
{
	const size_t N = 10000;
	vector<int> v1;
	RBTree<int, int> t1;
	srand(time(0));
	for(int i = 0; i < N; i++)
	{
		v1.push_back(rand());
	}
	for(auto : v1)
	{
		t1.insert(make_pair(e, e));
	}
	cout << t1.IsBalance() << endl;
}

在这里插入图片描述
当我们输出所有路径的黑色节点数量时,发现,这数据有点多,我们看不过来,不方便检查。
这里我们的思路是,除了传入 blacknum, 我们还需要传入一个判断的值

        bool Check(Node* root, int blacknum, const int refVal)
        {
                if (root == nullptr)
                {
                        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) && Check(root->_right, blacknum);
        }
        bool IsBalance()
        {
                return _IsBalance(_root);
        }
private:
        bool _IsBalance(Node* root)
        {
                if (root->_col == RED)
                {
                        return false;
                }
                int blacknum = 0;
                int refVal = 0;
                Node* cur = root;
                while (cur)
                {
                        if (cur->_col = BLACK)
                        {
                                refVal++;
                        }
                        cur = cur->_left;
                }
                return Check(root, blacknum, refVal);
        }

在这里插入图片描述
这里我们看见,在debug 版本下,插入和检查的时间一共用了 309ms,非常快
在这里插入图片描述
release 版本下,用了 119ms
在这里插入图片描述
100w 的数据一共是 18 层
注:这里看起来和 AVL 树高度差不多,其实是因为 rand 是伪随机,给的数值在量很大的情况下,会出现重复,所以表面上我们插入了 100w 个数据,实际上数据早就饱和了

2.4. erase

删除节点
删除的情况比插入的情况多很多,和AVL树一样,我们还是简单说一下,不实现
在这里插入图片描述
如果我们要删除 红色节点
在这里插入图片描述
这没啥影响,那5条规则没有受到破坏
如果删除的黑色节点
在这里插入图片描述
删除 黑色节点,我们发现,这个节点所在的路径黑色节点减少了,这里仅靠变色无法解决
在这里插入图片描述
25变红,17变黑?但是这样会导致出现连续的红色节点。
最右边的两条路径不管怎么样都会含有一个黑色节点,但是左边这条路径不存在黑色节点,所以需要旋转解决,这里还好看一点,右边整体高,右右左单旋。
在这里插入图片描述
删除时,如果出现问题,和插入一样,优先考虑 变色,后考虑旋转,但是旋转一定要放在 变色前。
删除的情况比较多,这里就不细说了

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

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

相关文章

Dockerfile镜像实例

目录 一、构建SSH镜像 1. 建立工作目录 2. 生成镜像 3. 启动容器并修改root密码 二、systemctl镜像 1. 建立工作目录 2. 生成镜像 3. 运行镜像容器 ​编辑 4. 测试容器systemct 三、Nginx镜像 1. 建立工作目录 2. 编写Dockerfile脚本 3. 编写run.sh启动脚本 4. …

IDEA启动Tomcat启动失败:jar包未部署【部署jar包】

IDEA启动Tomcat报错java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener&#xff1a;jar包未部署【部署jar包】 学习java&#xff0c;开始跟着教程的步伐学习maven下载jar包&#xff0c;tomcat启动项目&#xff0c;发现项目未启动成功也…

虾皮(Shopee)商品详情API接口:轻松获取商品深度信息

API接口概述 虾皮的商品详情API接口是专为商家和开发者提供的服务接口&#xff0c;通过该接口&#xff0c;您可以快速、准确地获取指定商品的详细信息。这些信息包括但不限于商品标题、价格、库存、描述、图片、规格参数等&#xff0c;为您的商品展示、比价、推荐等场景提供有…

C++设计模式-结构型设计模式

写少量的代码来应对未来需求的变化。 单例模式 定义 保证一个类仅有一个实例&#xff0c;并提供一个该实例的全局访问点。——《设计模式》GoF 解决问题 稳定点&#xff1a; 类只有一个实例&#xff0c;提供全局的访问点&#xff08;抽象&#xff09; 变化点&#xff1a…

SpringCloud微服务:Eureka 和 Nacos 注册中心

共同点 都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测 不同点 Nacos 支持服务端主动检测提供者状态&#xff1a;临时实例采用心跳模式&#xff0c;非临时&#xff08;永久&#xff09;实例采用主动检测模式Nacos 临时实例心跳不正常会被剔除&#xff0c;非临时实…

【uniapp】H5+、APP模拟浏览器环境内部打开网页

前言 今天将智能体嵌入到我的项目中&#xff0c;当作app应用时&#xff0c;发现我使用的webview组件&#xff0c;无论H5怎么登录都是未登录&#xff0c;而APP却可以&#xff0c;于是进行了测试&#xff0c;发现以下几种情况&#xff1a; 方法<a>标签webviewAPP✅✅网页…

YOLOv5改进之bifpn

目录 一、原理 二、代码 三、在YOLOv5中的应用 一、原理 论文链接:

课题学习(二十三)---三轴MEMS加速度计芯片ADXL372

声明&#xff1a;本人水平有限&#xff0c;博客可能存在部分错误的地方&#xff0c;请广大读者谅解并向本人反馈错误。 一、基础配置 测量范围-200g-200g&#xff0c;分辨率为12位&#xff0c; V s 、 V D D I / O V_s、V_{DDI/O} Vs​、VDDI/O​范围为1.6V-3.5V 1.1 引脚配…

【银角大王——Django课程——用户表的基本操作2】

用户表的基本操作2 编辑用户按钮删除按钮入职日期——不显示时分&#xff0c;只显示年月日——使用DataField函数不使用DateTimeField修改models记得重新执行命令&#xff0c;更新数据库结构修改前修改后 编辑用户按钮 点击编辑&#xff0c;跳转到编辑页面&#xff08;将编辑的…

CrossOver支持的软件多吗 CrossOver支持软件列表 crossover兼容性查询

如果你是一个喜欢在Mac上工作的用户&#xff0c;但又不想放弃一些Windows上的优秀软件&#xff0c;那么可以考虑使用一些兼容工具来运行Windows程序。其中&#xff0c;CrossOver就是一款功能强大且受欢迎的兼容工具。那么&#xff0c;CrossOver到底能支持哪些Windows软件呢&…

JVM笔记2--垃圾收集算法

1、如何确认哪些对象“已死” 在上一篇文章中介绍到Java内存运行时的各个区域。其中程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生&#xff0c;随线程而灭&#xff0c;栈中的栈帧随着方法的进入和退出而有条不紊的执行着入栈和出栈操作。每个栈帧中分配多少内存基本上…

VMvare如何更改虚拟机内共享文件夹的挂载点

更改虚拟机内共享文件夹的路径 进入目录 /etc/init.d ,并找到vmware-tools文件 里面有配置项 vmhgfs_mnt"/mnt/hgfs" 将引号内的内容更改为你需要挂载的路径,重启即可 注意挂载的路径不能是 “/”&#xff0c;必须根目录下的某个文件夹&#xff0c;或者其子文件夹 …

定时器编程前配置和控制LED隔一秒亮灭

1.配置定时器 0 工作模式16位计时 2.给初值&#xff0c;定一个10ms出来 3.开始计时

环形链表的判断方法与原理证明

&#xff08;题目来源&#xff1a;力扣&#xff09; 一.判读一个链表是否是环形链表 题目&#xff1a; 解答&#xff1a; 方法&#xff1a;快慢指针法 内容&#xff1a;分别定义快慢指针&#xff08;fast和slow&#xff09;&#xff0c;快指针一次走两步&#xff0c;慢指…

物体检测:如何检测小物体?

原文地址&#xff1a;https://medium.com/voxel51/how-to-detect-small-objects-cfa569b4d5bd 2024 年 4 月 22 日 物体检测是计算机视觉的基本任务之一。在高层次上&#xff0c;它涉及预测图像中物体的位置和类别。最先进的&#xff08;SOTA&#xff09;深度学习模型&#x…

3031087 -“无数据”:物料不显示在 MRP 应用中

症状 使用其中一个 MRP 应用&#xff08;监控物料覆盖范围、管理物料覆盖范围、监控外部需求等&#xff09;时无法找到物料。 用户在搜索过滤器时会收到错误消息“无数据”。 “本 KBA 中的图像/数据来自 SAP 内部系统、示例数据或演示系统。任何与真实数据相似的都是完全巧…

Apache反代理Tomcat项目,分离应用服务器和WEB服务器

项目的原理是使用单独的机器做应用服务器&#xff0c;再用单独的机器做WEB服务器&#xff0c;从网络需要访问我们的应用的话&#xff0c;就会先经过我们的WEB服务器&#xff0c;再到达应用程序&#xff0c;这样子的好处是我们可以保护应用程序的机器位置&#xff0c;同时还可以…

R语言中,查看经安装的包,查看已经加载的包,查看特定包是否已经安装,安装包,更新包,卸载包

创建于&#xff1a;2024.5.4 R语言中&#xff0c;查看经安装的包&#xff0c;查看已经加载的包&#xff0c;查看特定包是否已经安装&#xff0c;安装包&#xff0c;更新包&#xff0c;卸载包 文章目录 1. 查看经安装的包2. 查看已经加载的包3. 查看特定包是否已经安装4. 安装包…

java发送请求-http和https

http和https区别 1、http是网络传输超文本协议&#xff0c;client---- http------ server 2、httpshttpssl证书&#xff0c;让网络传输更安全 &#xff0c;client---- httpssl------ server 3、ssl证书是需要客户端认可的&#xff0c;注意官方证书和jdk生成的证书的用户来使…

实现批量自动文本标注(输出标签)代码复现

一&#xff1a;项目地址&#xff1a; IDEA-Research/Grounded-Segment-Anything: Grounded-SAM: Marrying Grounding-DINO with Segment Anything & Stable Diffusion & Recognize Anything - Automatically Detect , Segment and Generate Anything (github.com) 二…