【C++】详解红黑树并模拟实现

news2024/9/20 20:29:16

前言:

       上篇文章我们一起学习了AVL树比模拟实现,我们发现AVL树成功地把时间复杂度降低到了O(logN)。但是同时我们不难发现一个问题,在构建AVL树中我们也付出了不小的代价,频繁的旋转操作导致效率变低。为了解决这个问题,我们本章将迎来更为实用的红黑树,他在提高查找效率的同时也相对AVL树提高了构建树的效率,他的应用将更加广泛(比如map、set的底层实现就是红黑树)!

目录

(一)红黑树的概念

1、概念

2、特性

(二)红黑树的模拟实现

1、结点的定义

2、结点的查找实现(Find)

3、结点的插入实现(Insert)*重点

3.1前序问题

3.2具体流程

情况一:叔叔存在且为红——变色处理

情况二:叔叔不存在or叔叔存在且为黑——旋转+变色

 情况三:叔叔不存在or叔叔存在且为黑——双旋+变色(区别于情况二)

4、具体代码(Find+Insert)

(三)验证红黑树

1、中序遍历

2、验证红黑树几大特性

3、测试用例

(四)项目完整代码


(一)红黑树的概念

1、概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black,所以叫红黑树

通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

2、特性

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

思考:

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点
个数的两倍?
这里我们从路径最短的情况和最长的情况分析:
我们假设该红黑树每条路径黑色结点数是3
路径最短的情况:
这条路径没有红色结点,如下图:

路径最长的情况:
这条路径一个黑色节点接着一个红色节点,交相出现:
                                 

这时我们发现最短路径时,一条路径上有三个结点(情况一);最长路径时,一条路径上有五个节点(情况二)。所以可以保证其最长路径中节点个数不会超过最短路径节点个数的两倍。

(二)红黑树的模拟实现

1、结点的定义

我们还是沿用AVL树节点的三叉链定义方法,颜色我们用枚举来实现:

ps:这里一开始结点定义为红色的原因,我们在后文插入的时候会详细讲解。

2、结点的查找实现(Find)

在红黑树中的Find(查找)操作和之前二叉搜索树和AVL树实现方法一样,这里我们简单带过。

结点存的值比当前结点小就向左找,比当前结点大就向右找,最后遇到空节点表示没找到。

3、结点的插入实现(Insert)*重点

3.1前序问题

首先,我们解答上面留下的那个问题,

为什么新插入的结点要默认给红色的?

  • 假设我们默认给新增的结点黑色,那么一定违反上面红黑树的特性4(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 )。这样我们还要对其他路径进行修改黑色结点的数量。
  • 但是如果默认给红色,我们则是可能违反特性3(如果一个节点是红色的,则它的两个孩子结点是黑色的 )。为什么说是可能,因为如果他的父亲节点是黑色则不违反。就算违反了。每条路径黑色结点的数量还是一样的,我们只需要调整所在的那一条路径即可,代价相对小了许多。

3.2具体流程

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

(分别对应parent,grandfather,uncle的意思)

插入几大流程:

  • 按照搜索二叉树的查找方法,找到要插入的位置
  • 将新增结点插入到待插入位置
  • 如果需要调整则调整红黑树

其中需不需要调整主要是看叔叔颜色的情况。

下面具体分析:

我们先给出一个基本的图示

这里的三角形就相当于AVL树中的小长方形,代表了一部分子树。

情况一:叔叔存在且为红——变色处理

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

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

首先我们知道,红黑树调整主要看叔叔,第一步我们将父亲变成黑色,为了维护性质4,我们也要将叔叔的颜色变成黑色,同时也要将祖父结点的颜色变成红色,因为我们此时处理的不一定是整棵树,有可能是某个子树,如果此时不是子树,就需要将根节点变成黑色。这样我们就在局部调整颜色并保证了不破坏规则。

 

调整的部分可能是一整棵树,也可能是某一棵子树

  • 如果g是根节点,调整完成后,需要将g改为黑色
  • 如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整

 

情况一的情景下,我们只关心颜色的调整就可以在不破坏规则的情况下插入结点!


情况二:叔叔不存在or叔叔存在且为黑——旋转+变色

情况二可能就是从情况一调整变化得到的情况。

具体情况 1

当叔叔不存在时:

我们调整颜色总会破坏规则,此时结合上面长路径不会超过短路径二倍的特性我们分析,英爱使用到了和AVL树相似的旋转处理。

  • 此时是左边高了,我们对图示树进行右单旋,这里的旋转和AVL树中旋转的方式如出一辙,可以拿过来直接复用。
  • 同样的道理,如果是右边高了,我们就可以进行左单旋,旋转的方法也和AVL树中的如出一辙,也可拿过来直接复用。(这里我们就不像AVL树那样详细解释了,实现方法是一样的,如果读者不太了解可以看上一篇文章)

 具体情况 2

当叔叔存在且为黑时:

按照上文说明,这种情况必定是由情况一变化而来的。

此时也是旋转+变色

这样子才能保护好红黑树的结构。


 情况三:叔叔不存在or叔叔存在且为黑——双旋+变色(区别于情况二)

同样是 叔叔不存在 or 叔叔存在且为黑,为啥分为情况二和情况三呢?

上述旋转的情况都是单边的情况,也就是说,cur、p、g在一条线上的情况,若是出现了这三者不在一条直线的时候,单旋就解决不了问题了。

区别:

  • p是g的左,cur是p的左,那么是情况二 —— 单旋
  • p是g的左,cur是p的右,那么是情况三 —— 双旋

此时我们就引入了双旋

(这里我们可以结合上一篇AVL树来区别单旋和双旋)

  • 先以p为旋转点再以g为旋转点
  • 先变到情况二的样子
  • 再根据情况二来继续变

其实看完情况二和三有些人就会有疑问了,看上去每条路径上黑色结点树不一样啊,为什么要这样旋转变色?

再此之前我们推测过当叔叔存在且为黑时,一定是由情况一变来的,所以cur一开始是黑的,这个树并不违反性质,子树由情况一变化之后的子树结点的颜色也相应变化了,只是没有显示出来而已。

4、具体代码(Find+Insert)

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


	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			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);

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

		//调整颜色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)//u存在且为红
			{
				Node* uncle = grandfather->_right;

				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
			

			else//u不存在或者为黑,旋转加变色
			{
				if (cur == parent->_left)
				{
					//    g
					//  p   u
					//c

					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}

				else
				{
					//     g
					//   p   u
					//     c

					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}

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

				cur = grandfather;
				parent = cur->_parent;
			}

			else// :u不存在/u存在且为黑,旋转+变色
			{
				if (cur == parent->_right)
				{
					//     g
					//  u    p
					//          c
					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 true;
    }

(三)验证红黑树

老样子,我们在上一章模拟实现AVL树后用了几个函数来验证我们构建的树是否是AVL树,这里我们也要验证一下我们构建的这棵树是否是红黑树。

几个验证要点:

  • 这棵树是否是二叉搜索树
  • 根结点是否是黑色结点
  • 红色结点的子结点是否是黑色结点
  • 每条路径黑色结点树是否一样

满足这几个要点,也就是红黑树的特性,那么这棵树1就是红黑树了。

1、中序遍历

一棵树是红黑树的前提就是他必须是二叉搜索树,二叉搜索树的特点就是中序遍历是有序的。

void InOrder()
	{
		return _InOrder(_root);
	}


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

ps:为了符合用户的使用习惯,我们还是采取嵌套的方式,大家不明白的可以看一下上一篇文章验证处有说明哦!

2、验证红黑树几大特性

相对AVL树,验证红黑树是更加麻烦的,几个要点:

  • 首先判断根结点是否是黑色
  • 判断每条路径黑色结点数我们思路是先取一条路径黑色结点数量为基准值,然后递归统计其他路径的数目,遇到空结点再比较
  • 遇到红色结点我们如果判断他的孩子结点是否是黑色,如果是要跳过(代码不好写),不如换个思路,遇到一个红色结点就判断双亲是否是红色,如果是(存在连续红色结点)返回false。

bool IsBalance()
	{
		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 _Check(_root, 0, benchmark);
	}


private:
	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

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

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

通过递归的方式将每条支路都走了一遍和基准值比较,并且将红黑树的性质全都验证了。

3、测试用例


void Test_RBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
		{
		int x = 0;
		}*/

		t1.Insert(make_pair(e, e));
		//cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << endl;
	cout << t1.IsBalance() << endl;
}


void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}

精检验,没有问题:

(四)项目完整代码

#pragma once
#include<iostream>
#include<assert.h>

using namespace std;

enum Colour
{
	RED,
	BLACK,
};

template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;


	RBTreeNode(const pair<K,V>& kv)
		:
	    _left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_col(RED)
	{}
};


template<class K,class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
	void Destroy(Node* root)
	{
		_Destroy(root);
		root = nullptr;
	}

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


	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			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);

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

		//调整颜色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)//u存在且为红
			{
				Node* uncle = grandfather->_right;

				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
			

			else//u不存在或者为黑,旋转加变色
			{
				if (cur == parent->_left)
				{
					//    g
					//  p   u
					//c

					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}

				else
				{
					//     g
					//   p   u
					//     c

					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}

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

				cur = grandfather;
				parent = cur->_parent;
			}

			else// :u不存在/u存在且为黑,旋转+变色
			{
				if (cur == parent->_right)
				{
					//     g
					//  u    p
					//          c
					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 true;
    }


	void InOrder()
	{
		return _InOrder(_root);
	}

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

	bool IsBalance()
	{
		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 _Check(_root, 0, benchmark);
	}


private:
	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

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

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

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	int _Height(Node* root)
	{
		if(root==nullptr)
		{
			return 0;
		}
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	
	void _Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}


	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 (ppnode == nullptr)
		{
			_root = subR;
			_root->_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 (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
private:
	Node* _root=nullptr;
};


void Test_RBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
		{
		int x = 0;
		}*/

		t1.Insert(make_pair(e, e));
		//cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << endl;
	cout << t1.IsBalance() << endl;
}


void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}

祝您学业有成!

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

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

相关文章

使用Fastchat部署vicuna大模型

FastChat是一个用于训练、提供服务和评估基于大型语言模型的聊天机器人的开放平台。其核心特点包括&#xff1a; 最先进模型&#xff08;例如 Vicuna&#xff09;的权重、训练代码和评估代码。一个分布式的多模型提供服务系统&#xff0c;配备 Web 用户界面和与 OpenAI 兼容的…

算法通关村第十七关:黄金挑战-跳跃游戏问题

黄金挑战-跳跃游戏问题 1. 跳跃游戏 LeetCode 55 https://leetcode.cn/problems/jump-game/ 思路分析 关键是判断能否到达终点&#xff0c;不用管每一步跳跃到哪里&#xff0c;而是尽可能的跳跃到最远的位置 看最多能覆盖到哪里&#xff0c;只要不断更新能覆盖的距离&#x…

【狂神】Spring5笔记(一)之IOC

目录 首页&#xff1a; 1.Spring 1.1 简介 1.2 优点 2.IOC理论推导 3.IOC本质 4.HelloSpring ERROR 5.IOC创建对象方式 5.1、无参构造 这个是默认的 5.2、有参构造 6.Spring配置说明 6.1、别名 6.2、Bean的配置 6.3、import 7.DL依赖注入环境 7.1 构造器注入 …

[JAVA] byte与int的类型转换案例剖析

总结&#xff1a; ①没有byte的字面值&#xff0c;赋值时需要强制转换类型 ②涉及运算&#xff0c;系统自动进行类型升级&#xff0c;由此用final修饰&#xff0c;代表这是一个不会更改值的常量&#xff0c;通过编译 感受&#xff1a;还是用int吧&#xff0c;自动类型转换太复…

VB:顺序查找

VB&#xff1a;顺序查找 Private Sub Command1_Click()Dim i%, m%Dim x(1 To 10) As SingleFor i 1 To 10x(i) Val(InputBox("请输入"))Next im seqSearch(x, 10)If (m 1) ThenPrint "已找到"ElsePrint "未找到"End If End Sub Function se…

为什么在线客服系统的消息撤回功能是有必要的?

如今在日常工作和沟通中&#xff0c;很多企业都在使用在线客服系统跟客户进行线上交流和协作。然而有时候客服可能会不小心发送错误的消息或包含敏感信息的消息&#xff0c;人们在现实的沟通交流中是不会真实存在“说出去的话还能收回来”的情况&#xff0c;但这是在网络上&…

thinkphp开启定时任务的三种办法(最全)

第一种方法 使用think-cron类库 //composer 安装 composer require yunwuxin/think-cron github文档地址 https://github.com/yunwuxin/think-cron 1.创建任务类 <?php namespace app\task; use yunwuxin\cron\Task; class DemoTask extends Task { public function …

【从入门到起飞】JavaSE—File的使用,构造方法,成员方法

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【The truth that you leave】 &#x1f970;欢迎并且感谢大家指出我的问题 文章目录 &#x1f354;File概述&#x1f354;File构造方法⭐根据文件路径…

el-carousel-item轮播一个swiper显示多个卡片数据

效果图&#xff1a; 图片路径均为假地址&#xff0c;需自行替换&#xff0c;1rem100px&#xff0c;可自行转换成px <template><div class"exhibitors page-item-blue-bg"><comItemTitle :titleInfo"titleInfo"> </comItemTitle>…

如何评估需求优先级?

项目的需求来源有很多方面&#xff0c;最终由产品经理整理出来哪些要做&#xff0c;哪些不做。我前面说过需求评审时&#xff0c;要讲清楚这次版本的目的是什么。这些要做的功能就是达成目的的手段。 一般情况下&#xff0c;我们都默认产品或技术总监给需求定优先级。比如优先…

微信小程序被坑记录

web-view组件 你想要使用它&#xff0c;必须必须要配置 开发管理->开发设置->业务域名 线上版本 wx.saveImageToPhotosAlbum 调用失败 前几天还能用&#xff0c;今天一试&#xff0c;妈耶&#xff0c;除了线上版本都能用。真的头大&#xff0c;线上版本咋看报错嘛 线…

Ei Scopus检索 | 2024年第四届能源与环境工程国际会议(CoEEE 2024)

会议简介 Brief Introduction 2024年第四届能源与环境工程国际会议(CoEEE 2024) 会议时间&#xff1a;2023年5月22日-24日 召开地点&#xff1a;意大利米兰 大会官网&#xff1a;www.coeee.org CoEEE 2024将围绕“能源与环境工程”的最新研究领域而展开&#xff0c;为研究人员、…

深度学习Tensorflow: CUDA_ERROR_OUT_OF_MEMORY解决办法

目前在用深度学习训练&#xff0c;训练中设置batch size后可以正常跑通&#xff0c;但是在训练一轮save_model时&#xff0c;总出现这个错误&#xff0c;即使我调batch size到1也依旧会报错。 发现是在 调用logger时出现问题。 查询后了解到是因为TensorFlow中的eager_executi…

VSCode学习笔记一:添加代码模板

一目了然 1 简述2 设置模板3 Global Snippets file示例 1 简述 问&#xff1a;为什么要设置代码模板&#xff1f; 答&#xff1a;编程语言是有个性的&#xff0c;不同语言的演讲风格是不一样的。 旁白&#xff1a;我不懂&#xff1f;&#xff01; 问&#xff1a;为什么要设置…

C++中引用详解!

前言&#xff1a; 本文旨在讲解C中引用的相关操作&#xff0c;以及引用的一些注意事项&#xff01;搬好小板凳&#xff0c;干货来了&#xff01; 引用的概念 何谓引用呢&#xff1f;引用其实很容易理解&#xff0c;比如李华这个同学&#xff0c;他因为很调皮&#xff0c;所以…

大模型时代下向量数据库的创新与变革

前言&#xff1a; 在当今信息时代&#xff0c;数据库扮演着关键的角色&#xff0c;用于存储和管理各种类型的数据。向量数据库是一种专门设计用于高维数据存储和快速检索的数据库系统。在不断创新和变革后&#xff0c;腾讯云不久前发布了AI原生&#xff08;AI Native&#xff0…

Vue3 + 百度地图实现位置选择,获取地址经纬度

Vue3 百度地图实现位置选择&#xff0c;获取地址经纬度 需求&#xff1a;添加传感器时&#xff0c;需要选择传感器所在的省、市、区、详细地址、以及传感器的经纬度信息。解决方案&#xff1a;集成百度地图API&#xff0c;通过在地图上搜索或者点击获取传感器的具体位置信息。…

什么是敏捷工作流程?如何实施?

依赖传统的项目管理流程&#xff08;即使它们效率不高&#xff09;&#xff0c;会阻碍团队协作&#xff0c;难以管理不断变化的项目需求。 而另一方面&#xff0c;现代项目管理方法&#xff08;如敏捷工作流程&#xff09;为项目带来了简单性、自主性和高效性。它能帮助你了解…

TSINGSEE青犀视频AI算法助力构建城市市容·街面秩序管理解决方案

随着城市化进程加快&#xff0c;未经合理规划设置自然形成的马路市场越来越多&#xff0c;这不仅存在交通安全隐患&#xff0c;也造成了市容秩序混乱&#xff0c;严重影响城市市容面貌。 TSINGSEE青犀AI智能分析网关V3内部部署了几十种算法&#xff0c;包括人脸、人体、车辆、…

fastjson漏洞复现

文章目录 启动环境漏洞复现下载bp插件漏洞扫描dnslog测试是否向外请求资源用工具构造rmi服务器 反弹shell 启动环境 到vulhub目录下 cd vulhub/fastjson/1.2.24-rce安装环境并启动&#xff1a; sudo docker-compose up -d && sudo docker-compose up -d启动成功&…