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

news2024/11/18 20:26:09

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变黑?但是这样会导致出现连续的红色节点。
最右边的两条路径不管怎么样都会含有一个黑色节点,但是左边这条路径不存在黑色节点,所以需要旋转解决,这里还好看一点,右边整体高,右右左单旋。
在这里插入图片描述
删除时,如果出现问题,和插入一样,优先考虑 变色,后考虑旋转,但是旋转一定要放在 变色前。
删除的情况比较多,这里就不细说了

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

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

相关文章

DIM层数据处理

一、了解DIM层 这个就是数仓开发的分层架构 我们现在是在DIM层&#xff0c;从ods表中数据进行加工处理&#xff0c;导入到dwd层&#xff0c;但是记住我们依然是在DIM层&#xff0c;而非是上面的ODS和DWD层。 二、处理维度表数据 ①先确认hive的配置 -- 开启动态分区方案 -- …

ubuntu20文件安装和卸载cuda11.6

搜索cuda 11.6 nvidia&#xff0c;进入官网https://developer.nvidia.com/cuda-11-6-0-download-archive 选择linux --> runfile 用安装包安装 wget https://developer.download.nvidia.com/compute/cuda/11.6.0/local_installers/cuda_11.6.0_510.39.01_linux.run sudo s…

飞书API(7):MySQL 入库通用版本

一、引入 在上一篇介绍了如何使用 pandas 处理飞书接口返回的数据&#xff0c;并将处理好的数据入库。最终的代码拓展性太差&#xff0c;本篇来探讨下如何使得上一篇的最终代码拓展性更好&#xff01;为什么上一篇的代码拓展性太差呢&#xff1f;我总结了几点&#xff1a; 列…

开源免费的网盘项目Cloudreve,基于Go云存储个人网盘系统源码(七牛、阿里云 OSS、腾讯云 COS、又拍云、OneDrive)

项目简介&#xff1a; 在现今的网盘服务中&#xff0c;用户经常遭遇限速和价格上涨的问题&#xff0c;这无疑增加了使用上的困扰。 为此&#xff0c;我今天要介绍一款开源且免费的网盘项目——Cloudreve。 这个项目是基于Go语言开发的云存储个人网盘系统&#xff0c;支持多种…

免费开源,无需 GPU,本地化部署大语言模型的对话系统

免费开源&#xff0c;无需 GPU&#xff0c;本地化部署大语言模型的对话系统 分类 编程技术 项目名: FreeAskInternet -- 本地化部署大语言模型的对话系统 Github 开源地址&#xff1a; https://github.com/nashsu/FreeAskInternet FreeAskInternet 是一个免费开源的工具&am…

「 网络安全常用术语解读 」通用漏洞报告框架CVRF详解

1. 背景 ICASI在推进多供应商协调漏洞披露方面处于领先地位&#xff0c;引入了通用漏洞报告框架&#xff08;Common Vulnerability Reporting Format&#xff0c;CVRF&#xff09;标准&#xff0c;制定了统一安全事件响应计划&#xff08;USIRP&#xff09;的原则&#xff0c;…

Python中无法pip的解决办法和pip的介绍

什么是pip&#xff1f; PIP是通用的Python包管理工具&#xff0c;提供了对 Python 包的查找、下载、安装、卸载、更新等功能。安装诸如Pygame、Pymysql、requests、Django等Python包时&#xff0c;都要用到pip。 注意&#xff1a;在Python3.4&#xff08;一说是3.6&#xff09…

Electron 对 SQLite 进行加密

上一篇讲了如何在 Electron使用 SQLite&#xff0c;如果 SQLite 中存有敏感数据&#xff0c;客户端采用明文存储风险很高&#xff0c;为了保护客户数据&#xff0c;就需要对数据进行加密&#xff0c;由于 electron 对代码并不加密&#xff0c;所以这里排除通过逆向工程进行数据…

ArcGIS软件:地图投影的认识、投影定制

这一篇博客介绍的主要是如何在ArcGIS软件中查看投影数据&#xff0c;如何定制投影。 1.查看地图坐标系、投影数据 首先我们打开COUNTIES.shp数据&#xff08;美国行政区划图&#xff09;&#xff0c;并点击鼠标右键&#xff0c;再点击数据框属性就可以得到以下的界面。 我们从…

深入理解分布式事务⑨ ---->MySQL 事务的实现原理 之 MySQL 中的XA 事务(基本原理、流程分析、事务语法、简单例子演示)详解

目录 MySQL 事务的实现原理 之 MySQL 中的XA 事务&#xff08;基本原理、流程分析、事务语法、简单例子演示&#xff09;详解MySQL 中的 XA 事务1、XA 事务的基本原理1-1&#xff1a;XA 事务模型图&#xff1a;1-2&#xff1a;XA 事务模型的两阶段提交操作&#xff1a;Prepare …

MLP手写数字识别(3)-使用tf.data.Dataset模块制作模型输入(tensorflow)

1、tensorflow版本查看 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、MNIST数据集下载与预处理 (train_images,train_labels),(test_images,test_labels) tf.keras.datasets.mnist.load_data()…

02_Java综述

目录 面向对象编程两种范式抽象OOP 三原则封装继承多态多态、封装与继承协同工作 面向对象编程 面向对象编程(Object-Oriented Programming&#xff0c;OOP)在Java中核心地位。几乎所有的Java程序至少在某种程度上都是面向对象的。OOP与java是密不可分的。下面说一下OOP的理论…

【已解决】VSCode 连接远程 Ubuntu :检测到 #include 错误。请更新 includePath。

文章目录 1. 环境声明2. 解决过程 1. 环境声明 即使是同一个报错&#xff0c;在不同的环境中&#xff0c;报错原因、解决方法都是不同的&#xff0c;本文只能解决跟我类似的问题&#xff0c;如果你发现你跟我遇到的问题不太一样&#xff0c;建议寻找其他解法。 必须要吐槽的是…

吴恩达2022机器学习专项课程C2(高级学习算法)W1(神经网络):2.1神经元与大脑

目录 神经网络1.初始动机*2.发展历史3.深度学习*4.应用历程 生物神经元1.基本功能2.神经元的互动方式3.信号传递与思维形成4.神经网络的形成 生物神经元简化1.生物神经元的结构2.信号传递过程3.生物学术语与人工神经网络 人工神经元*1.模型简化2.人工神经网络的构建3.计算和输入…

基于51单片机的智能台灯proteus仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

基于51单片机的红外光敏检测智能台灯控制系统仿真( proteus仿真程序原理图报告讲解视频&#xff09; 1.主要功能&#xff1a; 基于51单片机的红外检测光照检测智能台灯仿真设计 1、检测光照强度并显示在数码管上。 2、具备红外检测人体功能。 3、灯光控制模式分为自动模式…

RabbiMQ(Docker 单机部署)

序言 本文给大家介绍如何使用 Docker 单机部署 RabbitMQ 并与 SpringBoot 整合使用。 一、部署流程 拉取镜像 docker pull rabbitmq:3-management镜像拉取成功之后使用下面命令启动 rabbitmq 容器 docker run \# 指定用户名-e RABBITMQ_DEFAULT_USERusername \# 指定密码-e R…

golang for经典练习 金字塔打印 示例 支持控制台输入要打印的层数

go语言中最经典的for练习程序 金字塔打印 &#xff0c;这也是其他语言中学习循环和条件算法最为经典的联系题。 其核心算法是如何控制内层循环变量j 每行打印的*号数量 j<i*2-1 和空格数量 j1 || j i*2-1 golang中实现实心金字塔 Solid Pyramid和空心金字塔 Hollow Pyram…

上位机图像处理和嵌入式模块部署(树莓派4b使用lua)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 lua是一个脚本语言&#xff0c;比c语言开发容易&#xff0c;也没有python那么重&#xff0c;整体使用还是非常方便的。一般当成胶水语言进行开发&a…

ASP.NET 两种开发模式

1》》WebForm 开发模式 1. 服务器端控件 2. 一般处理程序html静态页Ajax 3. 一般处理程序html模板 如下图 2》》MVC 太复杂的系统&#xff0c;会造成Controller 过复杂。 后来就诞生了 MVP、MVVM等模式

C语言 计数控制循环

今天 我们来说 计数控制的循环 对于循环次数 我们已知的循环 我们称之为 计数控制的循环 这种情况 我们一般选择 for来实现 更为方便 先看一个案例 求 1 到 N 的累加合 我们代码可以这样写 #define _CRT_SECURE_NO_WARNINGS//禁用安全函数警告 #pragma warning(disable:6031…