二叉树搜索树详解

news2024/11/14 20:45:04

定义

二叉搜索树(BST,Binary Search Tree)

  • 或为一颗空树,或满足一下性质
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

注:二叉树中元素是单一的,无重复

例子

  • arr = { 8,3,1,4,6,10,7,14,13}
    在这里插入图片描述

二叉搜索树的功能及实现

节点

struct BSNode
{
	BSNode(const K& data)
		:_left(nullptr)
		, _right(nullptr)
		, _key(data)
	{}

	BSNode<K>* _left;
	BSNode<K>* _right;
	K _key;
};

如上文讨论,二叉搜索树是一种二叉树,节点与普通二叉树相似

基本结构

class BinarySearchTree
{
	typedef BSNode<K> Node;
	typedef Node* PNode;

	public:
		//构造函数
		BinarySearchTree();
		//拷贝构造
		BinarySearchTree(const BinarySearchTree<K>& t);
		//赋值重载
		BinarySearchTree<K>& operator=(BinarySearchTree<K> t);
		//析构函数
		~BinarySearchTree();
		//插入
		bool Insert(const K& data);
		//插入的递归实现
		bool InsertR(const K& data);
		//中序遍历
		void InOrder();
		//删除
		bool Erase(const K& data);
		//删除的递归实现
		bool EraseR(const K& data);
		//查找
		bool Find(const K& data);
		//查找的递归实现
		bool FindR(const K& data);
		//树的销毁(用以辅助析构函数)
		void Destory();
		private:
			PNode _root = nullptr;
	};

构造函数

BinarySearchTree()
		{}

因以给与_root以默认值,构造函数起始可以省略
该默认值等效为

BinarySearchTree():_root(nullptr)
		{}

使用构造函数构造一个空树即可

拷贝构造

拷贝一棵新树即可

		BinarySearchTree(const BinarySearchTree<K>& t)
		{
			_root = _Copy(t._root);//调用_Copy函数递归构造树
		}
		PNode _Copy(PNode root)
		{
			if (root == nullptr)//当root节点为空树时,返回nullptr
			return nullptr;
			//拷贝根节点
			PNode CopyRoot = new Node(root->_key);
			//拷贝并链接左子树
			CopyRoot->_left = _Copy(root->_left);
			//拷贝并链接右子树
			CopyRoot->_right = _Copy(root->_right);
			//返回新拷贝出的子树
			return CopyRoot;
	}

赋值重载

因拷贝构造已经实现,在函数传参时,非传引用情况下,函数参数会调用拷贝构造去构造一棵新树,交换指针即可
在这里插入图片描述

拷贝出的树会于函数结束后自动析构

		BinarySearchTree<K>& operator=(BinarySearchTree<K> t)
		{
			std::swap(_root, t._root);
			return *this;
		}

析构函数

析构函数也通过递归调用实现

		~BinarySearchTree()
		{
			_Destory(_root);//销毁二叉搜索树
			_root = nullptr;//并将_root置空,防止越界访问

		}
		void _Destory(PNode& root)
		{
			if (root == nullptr)//空树直接返回
				return;
			_Destory(root->_left);//销毁左子树
			_Destory(root->_right);//销毁右子树
			delete root;
		}

插入

常规实现

按照二叉搜索树的性质对元素进行插入
通过指针cur遍历二叉搜索树,pcur指针保存cur的父节点指针

bool Insert(const K& data)
		{
			if (nullptr == _root)//判断是否为空树
			{
				_root = new Node(data);
				return true;
			}
			//通过cur遍历二叉搜索树,cur最后指向的位置及为data插入位置
			//pcur保存cur的父节点,最后通过pcur链接新节点
			//_root及是整颗树的根节点,无父节点,先将pcur置空
			PNode pcur = nullptr;
			PNode cur = _root;
			//当cur == nullptr时,找到目标位置
			while (cur)
			{
				//保存cur当前位置
				pcur = cur;
				K& tmp = cur->_key;
				
				//进行数值判断
				if (data > tmp)
					cur = cur->_right;
				else if (data < tmp)
					cur = cur->_left;
				//相等时,插入失败
				else
					return false;
			}
			//虽然将cur的父节点传回,但不知为该节点的左子树还是右子树
			if (data > pcur->_key)
				pcur->_right = new Node(data);
			else
				pcur->_left = new Node(data);
			return true;
		}

递归实现

如果为空树,则new一个新节点直接赋值给_root
非空树时,按照性质进行插入

  • 插入值与节点值相同时,插入节点失败
  • 插入值大于节点值时,将节点插入右子树
  • 插入值小于节点值时,将节点插入左子树
    重复此操作直至寻到合适位置,将位置传给_InsertR()

传指针时需注意传递的为引用,通过引用十分方便的将父节点与子节点链接了起来

		bool InsertR(const K& data)
		{
			//递归插入需传递位置
			//对函数进行进一步封装
			return _InsertR(_root, data);
		}
		
		bool _InsertR(PNode& root, const K& data)
		{
			//	root 为空存在两种情况
			//其一:树为空树,new一个新节点并链接
			//其二:root当前位置为插入节点的目标所在
			//因root实际上传的为引用
			//对root的改变直接改变父节点中的指针
			//十分方便的实现了节点间的链接
			if (root == nullptr)
			{
				root = new Node(data);
				return true;
			}
			//判断data值的位置
			else if (data > root->_key)
				return _InsertR(root->_right, data);
			else
				return _InsertR(root->_left, data);
			return false;
		}

删除

二叉搜索树的删除是十分复杂的
二叉搜索树具有独特的结构,删除结点的同时,也要保持树独特结构的保存
当删除一个节点时,可以分成一下四种情况

  • 要删除的结点无孩子结点
  • 要删除的结点只有左孩子结点
  • 要删除的结点只有右孩子结点
  • 要删除的结点有左、右孩子结点

第一种情况可以和第二种或者第三种合并
实际分类起始为三种

  • 要删除的结点只有右孩子结点 直接删除,将父节点链接右孩子节点
  • 要删除的结点只有左孩子结点 直接删除,将父节点链接左孩子节点
  • 要删除的结点有左、右孩子结点 较为复杂,后文讨论

我们可以将节点的删除分为两个部分

  1. 找到节点
  2. 删除节点

非递归实现

找节点的过程与插入节点的过程相似

		bool Erase(const K& data)
		{
			if (_root == nullptr)
				return false;
			PNode pcur = nullptr;
			PNode cur = _root;
			while (cur)
			{
				K& tmp = cur->_key;

				if (data > tmp)
				{
					pcur = cur;
					cur = cur->_right;
				}
				else if (data < tmp)
				{
					pcur = cur;
					cur = cur->_left;

				}
				else
				{
					//找到节点了
				}
			}
			return false;

		}

查找完成后
cur指针指向的为目标节点
pcur指针指向的为目标节点的父节点

第二步删除节点
据上文讨论删除节点可分三种情况

第一种要删除的结点只有右孩子结点
直接删除,将父节点链接右孩子节点

但也存在一些特殊情况,一般情况下pcur指向的是cur的父节点
但当cur指向的是根节点时,根节点无父节点
此时据查找函数可知,此时pcur为nullptr
在这里插入图片描述

		//该节点的左孩子为空
		if (cur->_left == nullptr)
		{
			//cur为根节点的特殊情况
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{	
				//判断cur是pcur的左子树还是右子树
				if (pcur->_left == cur)
					pcur->_left = cur->_right;
				else
					pcur->_right = cur->_right;
			}
				//删除cur节点,并将指针置空
				delete cur;
				cur = nullptr;
			}

第二种要删除的结点只有左孩子结点
直接删除,将父节点链接左孩子节点
整体上与第一种类似

		//该节点的右孩子为空
		else if (cur->_right == nullptr)
		{
			//cur为根节点的特殊情况
			if (cur == _root)
			{
				_root = cur->_left;
			}
			else
			{
				//判断cur是pcur的左子树还是右子树
				if (pcur->_left == cur)
					pcur->_left = cur->_left;
				else
					pcur->_right = cur->_left;
			}
				//删除cur节点,并将指针置空
				delete cur;
				cur = nullptr;
		}

第三种情况要删除的结点有左、右孩子结点
这种情况是最复杂的
删除该节点且要保证,二叉搜索树的结构不被破坏

在这里插入图片描述
删除这个节点且要保证结构不破坏
该如何实现呢?
该节点被删除后,还有两颗子树需要处理
比较好的方案便是补上该节点的位置
补上该位置的节点有两点要求

  1. 要大于左节点的值
  2. 要小于右节点的值

有两种方案均满足需求

  1. 取右子树的最小节点
  2. 取左子树的最大节点

本文采取第一种方案
取右子树的最小节点
因右子树中所有元素均大于根节点
左子树中所有元素均小于根节点
右子树中的最小节点能很好的解决这个问题

此问题转化为 取右子树中的最小值
根据平衡二叉树的性质 左子树的值小于根节点
最小的节点一定是位于树的最左侧的

使用parent标记右子树中最小节点的父节点
使用min 标记右子树中最小节点

		else
		{
			//查找cur右子树的最小子节点
			PNode parent = cur;
			PNode min = cur->_right;
			//min->left 等于nullptr时 min已经为最小节点
			while (min->_left != nullptr)
			{
				parent = min;
				min = min->_left;
			}
			
			//交换 cur 的值与 min 的值
			std::swap(min->_key, cur->_key);
			//min是parent的左孩子时
			if (parent->_left == min) 
			parent->_left = min->_right;
			//min是parent的右孩子
			//min是parent的右孩子,只有一种情况
			//即右子树只有一个节点
			//parent指向cur min指向cur->_right;
			else
			parent->_right = min->_right;
			delete min;
			return true;
		}
非递归删除的完整代码
		bool Erase(const K& data)
		{
			if (_root == nullptr)
				return false;
			PNode pcur = nullptr;
			PNode cur = _root;
			while (cur)
			{
				K& tmp = cur->_key;

				if (data > tmp)
				{
					pcur = cur;
					cur = cur->_right;
				}
				else if (data < tmp)
				{
					pcur = cur;
					cur = cur->_left;

				}
				else
				{
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (pcur->_left == cur)
								pcur->_left = cur->_right;
							else
								pcur->_right = cur->_right;
						}
						delete cur;
						cur = nullptr;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (pcur->_left == cur)
								pcur->_left = cur->_left;
							else
								pcur->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					else
					{
						//查找cur右子树的最小子节点
						PNode parent = cur;
						PNode min = cur->_right;
						while (min->_left != nullptr)
						{
							parent = min;
							min = min->_left;
						}
						std::swap(min->_key, cur->_key);
						if (parent->_left == min) 
						parent->_left = min->_right;
						else 
						parent->_right = min->_right;
						delete min;

						return true;

					}
				}
			}
			return false;

		}

递归删除

递归删除的的思路如下
使用root传递需要删除元素所在树,同归递归缩小目标范围
通过data传递元素值

当目标元素小于根节点的值时,去左子树搜索
当目标元素大于根节点的值时,去右子树搜索

如果查到空树也没有找到该元素,返回false
查到该元素时,具体的删除思路与非递归一致
使用右子树的最小值或者左子树的最大值
但也存在一些差异,在代码中具体说明

		bool EraseR(const K& data)
		{
			//递归删除,需要传递位置
			//对删除函数进行进一步封装
			return _EraseR(_root,data);
		}
		bool _EraseR(PNode& root, const K& data)
		{
			//空树,查找失败
			if (root == nullptr)
				return false;
			//元素大于节点值,去右子树中查找
			if (data > root->_key)
				return _EraseR(root->_right,data);
			//元素小于节点值,去左子树中查找
			else if (data < root->_key)
				return _EraseR(root->_left,data);
			//找到目标元素
			else
			{
				//元素的三种情况
				//该元素没有左孩子
				PNode tmp = root;
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				//该元素没有右孩子
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				//该元素存在左孩子与右孩子
				else
				{	
					PNode min = root->_right;
					PNode parent = root;
					while (min->_left)
					{
						parent = min;
						min = min->_left;
					}
					std::swap(min->_key, root->_key);
					//上文代码与非递归相同
					//一下是两种删除节点的方法
					//第一种与非递归相同
					/*if (parent->_left == min)
						parent->_left = min->_right;
					else 
						parent->_right = min->_right;
					delete min;
					return true;*/
					//第二章起始为递归调用了_EraseR
					//被用以与目标元素替换的元素,他的子树数量一定小于等于1
					//调用删除代码删除时,不会走到此处
					return _EraseR(root->_right, data);
				}
			}
		}

查找

查找的具体思考,起始在删除与插入均均有使用

  • 树为空树,查找失败,返回false
  • 元素大于根节点,向右子树中查询
  • 元素小于根节点,向左子树中查询
  • 找到目标元素,返回true

非递归实现

		bool Find(const K& data)
		{
			PNode cur = _root;
			while (cur)
			{
				//元素与节点值相同,查询成功
				if (cur->_key == data)
					return true;
				//元素大于节点值,去右子树中查询
				else if (data > cur->_key)
					cur = cur->_right;
				//元素小于节点值,去左子树中查询
				else
					cur = cur->_left;
			}
			return false;
		}

递归实现

		bool FindR(const K& data)
		{
			return _FindR(_root, data);
		}
		bool _FindR(PNode root, const K& data)
		{
			//空树查找失败
			if (root == nullptr) 
				return false;
			//查找成功
			if (root->_key == data) 
				return true;
			//元素大于节点值,右子树中查询
			else if (root->_key < data)
				return _FindR(root->_right, data);
			//元素小于节点值,左子树中查询
			else
				return _FindR(root->_left, data);
		}

中序遍历

二叉树的中序遍历,得到的元素是一个升序

二叉搜索树中所有的非空节点均满足一下性质
左节点非空时,一定小于该节点
右节点非空时,一定大于该节点

当对二叉搜索树进行中序遍历时,依照中序遍历的顺序

  • 左子树
  • 根节点
  • 右子树

根据二叉搜索树的性质可知。
左子树中所有元素小于根节点,右子树中所有节点大于根节点
如果按照先遍历左子树,再遍历根节点,最后遍历右子树
那么此时根节点的位置的左侧均小于根节点,右侧均大于根节点
在这里插入图片描述

及为将树中元素按照升序排列时,根节点中元素应在的位置的位置

当遍历左子树与右子树时,也按照左子树,根节点,右子树的顺序遍历时
所有元素均处于升序数组中的正确位置
及中序遍历二叉搜索树时,遍历出的顺序为升序

递归实现

	void InOrder()
	{
		_InOrder(_root);
	}
	void _InOrder(PNode root)
	{
		if (root == nullptr)
		 return;
		_InOrder(root->_left);
		std::cout << root->_key << " ";
		_InOrder(root->_right);
	}

代码总览

#pragma once
#include<iostream>
template<class K>
struct BSNode
{
	BSNode(const K& data)
		:_left(nullptr)
		, _right(nullptr)
		, _key(data)
	{}

	BSNode<K>* _left;
	BSNode<K>* _right;
	K _key;
};
template<class K>
class BinarySearchTree
{
	typedef BSNode<K> Node;
	typedef Node* PNode;

	public:
		~BinarySearchTree()
		{
			_Destory(_root);
			_root = nullptr;
		}
		BinarySearchTree()
		{}
		BinarySearchTree(const BinarySearchTree<K>& t)
		{
			_root = _Copy(t._root);
		}
		BinarySearchTree<K>& operator=(BinarySearchTree<K> t)
		{
			std::swap(_root, t._root);
			return *this;
		}

		bool Insert(const K& data)
		{
			if (nullptr == _root)
			{
				_root = new Node(data);
				return true;
			}

			PNode pcur = nullptr;
			PNode cur = _root;
			while (cur)
			{
				pcur = cur;
				K& tmp = cur->_key;

				if (data > tmp)
					cur = cur->_right;
				else if (data < tmp)
					cur = cur->_left;
				else
					return false;
			}
			if (data > pcur->_key)
				pcur->_right = new Node(data);
			else
				pcur->_left = new Node(data);
			return true;
		}
		bool InsertR(const K& data)
		{
			return _InsertR(_root, data);
		}
		void InOrder()
		{
			_InOrder(_root);
		}
		bool Erase(const K& data)
		{
			if (_root == nullptr)
				return false;
			PNode pcur = nullptr;
			PNode cur = _root;
			while (cur)
			{
				K& tmp = cur->_key;

				if (data > tmp)
				{
					pcur = cur;
					cur = cur->_right;
				}
				else if (data < tmp)
				{
					pcur = cur;
					cur = cur->_left;

				}
				else
				{
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (pcur->_left == cur)
								pcur->_left = cur->_right;
							else
								pcur->_right = cur->_right;
						}
						delete cur;
						cur = nullptr;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (pcur->_left == cur)
								pcur->_left = cur->_left;
							else
								pcur->_right = cur->_left;
						}
						delete cur;
						cur = nullptr;
					}
					else
					{
						//查找cur右子树的最小子节点
						PNode parent = cur;
						PNode min = cur->_right;
						while (min->_left != nullptr)
						{
							parent = min;
							min = min->_left;
						}
						std::swap(min->_key, cur->_key);
						if (parent->_left == min) parent->_left = min->_right;
						else parent->_right = min->_right;
						delete min;

						return true;

					}
				}
			}
			return false;

		}
		bool EraseR(const K& data)
		{
			return _EraseR(_root,data);
		}
		bool Find(const K& data)
		{
			PNode cur = _root;
			while (cur)
			{
				if (cur->_key == data)
					return true;
				else if (data > cur->_key)
					cur = cur->_right;
				else
					cur = cur->_left;
			}
			return false;
		}
		bool FindR(const K& data)
		{
			return _FindR(_root, data);
		}
		void Destory()
		{
			_Destory(_root);
		}
		private:
			bool _EraseR(PNode& root, const K& data)
			{
				if (root == nullptr)
					return false;

				if (data > root->_key)
					return _EraseR(root->_right,data);

				else if (data < root->_key)
					return _EraseR(root->_left,data);

				else
				{
					PNode tmp = root;
					if (root->_left == nullptr)
					{
						root = root->_right;
					}
					else if (root->_right == nullptr)
					{
						root = root->_left;
					}
					else
					{	
						PNode min = root->_right;
						PNode parent = root;
						while (min->_left)
						{
							parent = min;
							min = min->_left;
						}
						std::swap(min->_key, root->_key);
						/*if (parent->_left == min)
							parent->_left = min->_right;
						else 
							parent->_right = min->_right;
						delete min;
						return true;*/
						return _EraseR(root->_right, data);
					}
				}
			}
			bool _InsertR(PNode& root, const K& data)
			{
				if (root == nullptr)
				{
					root = new Node(data);
					return true;
				}
				else if (data > root->_key)
					return _InsertR(root->_right, data);
				else
					return _InsertR(root->_left, data);
				return false;
			}
			bool _FindR(PNode root, const K& data)
			{
				if (root == nullptr) return false;
				if (root->_key == data) return true;
				else if (root->_key < data)
					return _FindR(root->_right, data);
				else
					return _FindR(root->_left, data);
			}
			void _InOrder(PNode root)
			{
				if (root == nullptr) return;
				_InOrder(root->_left);
				std::cout << root->_key << " ";
				_InOrder(root->_right);
			}
			void _Destory(PNode& root)
			{
				if (root == nullptr)
					return;
				_Destory(root->_left);
				_Destory(root->_right);
				delete root;
			}
			PNode _Copy(PNode root)
			{
				if (root == nullptr)
					return nullptr;
				PNode CopyRoot = new Node(root->_key);
				CopyRoot->_left = _Copy(root->_left);
				CopyRoot->_right = _Copy(root->_right);
				return CopyRoot;
			}
			PNode _root = nullptr;
	};

二叉搜索树的应用

二叉树有两种模型

*K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值
比如:给定一个单词,判断该单词是否拼写正确。具体方式如下:

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

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
比如:英汉词典就是英文与中文的对应关系,即<word, Chinese>就构成一种键值对。具体方式如下:

  1. 以<单词, 中文含义>为键值对,构建一棵二叉搜索树。注意:二叉搜索树需要进行比较,键值对比较时只比较key。
  2. 查询英文单词时,只需给出英文单词就可以快速找到与其对应的中文含义。

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

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

相关文章

单测时尽量用fake object

1. 单元测试的难点&#xff1a;外部协作者(external collaborators)的存在 单元测试是软件开发的一个重要部分&#xff0c;它有助于在开发周期的早期发现错误&#xff0c;帮助开发人员增加对生产代码正常工作的信心&#xff0c;同时也有助于改善代码设计。**Go语言从诞生那天起…

C++支持函数重载的原理--名字修饰

这章的原理并不难&#xff0c;前提是你要理解编译和链接。不会的会可以看一下这篇博客快速过一遍。 为什么C支持函数重载&#xff0c;而C语言不支持函数重载呢&#xff1f; 在C/C中&#xff0c;一个程序要运行起来&#xff0c;需要经历以下几个阶段&#xff1a;预处理、编译、汇…

Apache Flink ML 2.2.0 发布公告

来源 | Apache Flink 官方博客 翻译 | 林东 Apache Flink 社区很荣幸地宣布 Apache Flink ML 2.2.0 版本正式发布&#xff01;本次发布的版本重点添加了 Flink ML 中的特征工程算法。现在 Flink ML 包含了 33 个开箱可用的特征工程算法&#xff0c;可以支持很多常见的特征工程任…

springboot,Flowable 流程实例的激活与挂起(一)

一.简介 要实现流程实例的挂起和激活&#xff0c;首先要知道什么是流程实例的挂起和激活。 挂起&#xff1a; ①一个定义好的流程模板&#xff08;流程定义&#xff09;&#xff0c;如果挂起了&#xff0c;那么就无法据此创建新的流程实例。 ② 一个流程实例如果挂起了&#…

Pycharm必会小技巧,用好了都不用加班,效率翻2倍

学Python必用Pycharm&#xff0c;今天就来教大家11个Pycharm最常用的技巧&#xff0c;以及一些pycharm常用的快捷键&#xff0c;让你的写代码的效率翻2倍&#xff0c;以后都不用加班了&#xff01; 跟上老司机的车速&#xff01; 文章目录 一、常用小技巧1.设置代码字体。2.…

Nature:惊人的突破!科学家们成功破译人类嗅觉感应机制的奥秘!

加州大学旧金山分校&#xff08;UCSF&#xff09;的科学家们创造了第一张关于气味分子如何激活人类气味受体的分子水平的3D图片&#xff0c;这是破译嗅觉的关键一步&#xff0c;该成果打破了长期以来研究人员对嗅觉理解的僵局。 该研究成果于2023年3月15日发表在《Nature》&…

ch6_1计算机中运算方法

计算机中数的表示计算机的运算方法运算器的设计 参考教材 本章内容主要介绍&#xff0c;计算机中的运算方法 无符号数和有符号数数的定点表示和浮点表示定点运算浮点四则运算算术逻辑单元 1. 无符号数和有符号数 1.1 无符号数 1.2 有符号数 计算机中&#xff0c; 小数点…

java新版本新特性

2. Java8新特性&#xff1a;Lambda表达式 2.1 关于Java8新特性简介 Java 8 (又称为 JDK 8或JDK1.8) 是 Java 语言开发的一个主要版本。 Java 8 是oracle公司于2014年3月发布&#xff0c;可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与…

【华为OD机试真题】AI处理器组合(java)100%通过率

AI处理器组合 知识点数组 时间限制:1s空间限制:256MB限定语言:不限 题目描述: 某公司研发了一款高性能Al处理器。每台物理设备具备8颗Al处理器,编号分别为 0、1、2、3、4、5、6、7。编号0-3的处理器处于同一个链路中,编号4-7的处理器 处于另外一个链路中,不通链路中的…

双目立体匹配中的极线约束(Epipolar Constraint),本质矩阵(Essential Matrix),对极几何(2D-2D)

极线约束&#xff08;Epipolar Constraint&#xff09;&#xff0c;本质矩阵&#xff08;Essential Matrix&#xff09;&#xff0c;对极几何&#xff08;2D-2D&#xff09; 1. The Epipolar constraint2. Essential matrix E E E 考虑一个SLAM中一个常见的问题&#xff1a;如…

【C++11】左值、右值、将亡值

值类别 C表达式的两个独立的属性&#xff1a;类型、值类别&#xff1b; 值类别分为&#xff1a;左值、纯右值、将亡值&#xff1b; 有名字的将亡值->左值&#xff1b;没名字-->右值 左值 能用&取地址的表达式&#xff1b; 例如&#xff1a;int a&#xff1b;可以…

Python每日一练(20230422)

目录 1. 杨辉三角 &#x1f31f; 2. 最长回文子串 &#x1f31f;&#x1f31f; 3. 逆波兰表达式求值 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 杨…

转义字符(\)对JavaScript中JSON.parse的影响概述

转义字符(\)对JavaScript中JSON.parse的影响 按照ECMA262第五版中的解释&#xff0c;JSON是一个提供了stringify和parse方法的内置对象&#xff0c;前者用于将js对象转化为符合json标准的字符串&#xff0c;后者将符合json标准的字符串转化为js对象。json标准参考<a href&q…

垃圾收集器面试总结(二)

G1 收集器 G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。 被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点&#xff1a; 并行与并发&am…

chatgpt智能提效职场办公-ppt怎么转换成word文档

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 将PPT转换成Word文档有多种方法&#xff0c;以下是其中的一种&#xff1a; 打开PPT文件&#xff0c;并选择“文件”选项卡中的“另存为…

光流法Optical Flow,Lucas-Kanade方法,CV中光流的约束分析

光流法Optical Flow&#xff0c;Lucas-Kanade方法&#xff0c;CV中光流的约束分析 Multiple View Geometry1. Optical Flow Estimation2. The Lucas-Kanade Method2.1 Brightness Constancy Assumption2.2 Constant motion in a neighborhood2.3 Compute the velocity vector2.…

19 calloc 和 realloc 虚拟内存分配的调试

前言 前面提到了 malloc 虚拟内存分配相关的内容 malloc 虚拟内存分配的调试(1) malloc 虚拟内存分配的调试(2) 这里提 calloc 和 realloc, 这两个函数 虽然没有 malloc 使用频率那么高 但是 还是有很大的知名度的, 本文这里 我们来看一下 calloc 此函数传入两个参数, 第…

【系统集成项目管理工程师】项目成本管理

&#x1f4a5;十大知识领域&#xff1a;项目成本管理 主要考计算题 项目进度管理包括以下 4 个过程: 制订成本管理计划成本估算成本预算成本控制 一、制订成本管理计划 制订了项目成本结构、估算、预算和控制的标准 输入工具与技术输出项目管理计划项目章程事业环境因素组织过…

拼凑出来的低代码平台,真的好用吗?(浅谈行业怪象)

0️⃣前言 这几年低代码概念非常火热&#xff0c;市面上的低代码平台如雨后春笋应运而生&#xff0c;有许多身边的朋友对我说&#xff1a;“未来的研发方式一定是低代码的、低技术门槛的&#xff0c;低代码开发是一项技术革新。 ” 然而&#xff0c;就在我打算对它进行深入研究…

【业务数据分析】——十大常用数据分析方法

目录 一、数据分析方法 二、营销管理方法论 1、SWOT分析 2、PEST分析 3、4P理论 三、常用数据分析方法论 1、公式拆解 2、对比分析 3、A/Btest 4、象限分析 5、帕累托分析 6、漏斗分析 7、路径分析 8、留存分析 9、5W2H分析法 10、麦肯锡逻辑树分析法 一、数据…