【C++】二叉搜索树

news2024/11/28 23:39:37

前言

        hi~大家好呀,欢迎点进我的C++学习笔记~

我的前一篇C++笔记链接~

【C++】多态_柒海啦的博客-CSDN博客

本篇需要用到的基础二叉树C语言实现链接~

用c语言实现一个简单的链表二叉树_柒海啦的博客-CSDN博客_c语言建立二叉树链表

        我们知道,查找一个数据会有很多的方法。在以前,我们查找效率最快的也就是一个二分查找。但是二分查找的前提必须是一个有序的数组。那么,我们有没有什么好的结构来帮助我们去查找数据呢?

        现在就介绍一种利用非线性结构对每次插入的数据进行整理,从而能够达到查找最优效率为log_2 N,并且也为之后的容器map、set做好基础(AVL树和红黑树的实现前提)的二叉搜索树。

 (唯~φ(>ω<*) )

目录

一、二叉搜索树的实现思路

二叉搜索树概念

二叉树搜索树实现结构

二、二叉搜索树的实现

1.构造、析构

结点构造

构造 

拷贝构造 

析构

赋值重载

2.插入

非递归实现:

递归实现:

3.寻找

非递归实现:

递归实现:

4.删除

非递归实现:

 递归实现:

5.遍历

6.综合代码


一、二叉搜索树的实现思路

        首先,需要具备一定的二叉树基础,也就是对树的基本构造需要了解,感兴趣的可以看我文章顶部二叉树基础链接哦~

        那么我们如何利用二叉树这个数据结构实现一个便于我们查找数呢?

二叉搜索树概念

二叉搜索树又称二叉排序树,它可以是一个空树也可以具有如下的性质:

        1.如果右子树不为空,那么右子树上所有结点值大于根结点的值。

        2.如果左子树不为空,那么左子树上所以结点值小于根结点的值。

        3.左右子树的每个结点满足上述条件。

二叉树有key、和keyvalue版本。但是两者实现结构不冲突,均是以key值进行比较。

        根据上述特性,比如下面就是二叉搜索树:

         只要子树中一个不满足上述规则,也就不是二叉搜索树了:

         其实,看一个二叉树是否为搜索树,只需要中序遍历一下即可,查看数据是否从小到大排序,是那么就是了。

二叉树搜索树实现结构

        那么现在我们如何实现出这样的一个结构呢?在前面的基础二叉树C语言实现中我们知道,使用链式最佳,并且定义一个结点结构,有指向左和右子树的指针,以及存放值的(key就只有key一个,key-value就存在key和value成员),那么现在在二叉搜索树这个类型里,只需要保存根结点,然后进行插入、寻找、删除等操作即可。

         那么实现核心就是插入和删除后需要保持二叉搜索树的这个性质,寻找便就可以按照二叉搜索树的左小右大的顺序去寻找。因为二叉搜索树结点可以存key或者key-value。两者实现效果一致,为了方便下面只实现了key版本。

二、二叉搜索树的实现

        首先,为了区别key和keyvalue的实现,可以分别放到不同的命名空间哦~下面只演示key,放在namespace key中的哦~

1.构造、析构

结点构造

        在构造之前,首先我们需要的是结点类型,结点分为key/key-value,left指向左子树,right指向右子树。初始化时需要向此类型传值初始化key,左右子树置为空。其次注意key值弄成模板参数,这样便就可以模板类,适应更多的类型哦~

	template<class K>
	struct BStreeNode  // 二叉树节点
	{
		BStreeNode* left;
		BStreeNode* right;
		K key;
		BStreeNode(const K& k)
			:key(k)
			,left(nullptr)
			,right(nullptr)
		{}
	};

构造 

         现在就开始正式的写代码,确定名字为BSTree(BinarySearchTree),首先确定存在一个属性的变量:存结点的头指针。(在为了方便写结点类型的前提下,可以进行typedef)

	template<class K>
	class BStree
	{
		typedef BStreeNode<K> Node;
        // ......
    private:
        Node* _root = nullptr;  // 这样使用默认的构造函数即可
    }

        可以注意到,因为属性只有一个指针类型,所以我们给了缺省值,这样在默认构造函数中就能初始化,并且也不用自己写构造函数了。但是考虑到之后会写拷贝构造函数,这样的话不存在默认的构造函数,可以利用C++11的语法,给构造函数后面加上=default就让编译器也生成默认构造函数啦。

	public:
		BStree() = default;   // C++11 即使写其他构造函数,也会生成默认构造函数

拷贝构造 

        因为是二叉树,所以如果是要复制一份二叉树的话,可以利用前序遍历的办法,结合C++的特性传指针引用,递归的将每个结点给拷贝复制一份。当然是直接以当前结点值最为new新结点对象作为一份拷贝啦,递归终止条件就是nullptr了。另外,由于构造函数的特殊性,无法返回值,所以我们可以在私有区域定义一个子函数进行递归即可:

		BStree(const BStree<K>& b)
		{
			_BStree(_root, b._root);
		}
        // .......

    private:
        // 拷贝构造递归
		void _BStree(Node*& node1, const Node* node2)
		{
			if (node2 == nullptr) return;
			node1 = new Node(node2->key);
			_BStree(node1->left, node2->left);
			_BStree(node1->right, node2->right);
		}

析构

         析构可以和拷贝构造反着来,后序遍历,首先删除左子树的所有结点,在删除右子树的所有结点,最后删除根结点。同样的结合析构函数特点,需要弄一个子函数进行辅助~

		~BStree()
		{
			_delBStree(_root);
		}
        // ......
        // 递归清理资源
    private:
		void _delBStree(Node*& node)
		{
			if (node == nullptr) return;

			_delBStree(node->left);
			_delBStree(node->right);
			delete node;
			node = nullptr;
		}

析构和拷贝构造形参是引用是非常精华的存在哦~细细评味吧~~~ 

赋值重载

         既然讲到拷贝构造了,这里顺便也将赋值重载讲了。实际上,赋值和拷贝构造非常像,直接复用拷贝构造的逻辑即可,既然要复用,我们可以利用形参,传参发生拷贝构造来帮助我们完成这件事情:

		// 赋值重载 当一个资本家,让别人帮自己工作
		BStree& operator=(BStree<K> b)
		{
			swap(b);
			return *this;
		}
		// 内部交换属性函数
		void swap(BStree<K>& b)
		{
			Node* tmp = b._root;
			b._root = _root;
			_root = tmp;
		}
        // ......

        内部交换属性函数自己写哦~ 

2.插入

        现在来到了二叉搜索树第一个利用其特性的地方了。对于插入的一个数,我们判断其key值是比当前结点的key值大还是小,大的话就去右子树找,小的话就去左子树找。如果遇到相同,就不插入(不允许数据冗余)。一直到空,此时就可以对其插入。当前对于插入来说有递归和非递归实现:

非递归实现:

        对于上面的总体思路一致,利用c结点去走循环即可,只不过出来时有可能存在特殊情况,比如第一次插入,就应该在_root结点。除此之外需要c遍历的同时用p记录它上一次的父节点,最后判断是属于其父左还是右-(利用搜索二叉树特性)就可以插入了:

		// 插入 - 非递归
		bool insert(const K& k)
		{
			Node* n = new Node(k);

			Node* p = _root;
			Node* c = _root;
			while (c)
			{
				if (c->key > k)
				{
					p = c;
					c = c->left;
				}
				else if (c->key < k)
				{
					p = c;
					c = c->right;
				}
				else
					return false;  // 相等
			}
			if (p == nullptr) _root = n;
			else if (p->key > k) p->left = n;
			else p->right = n;
			return true;
		}

        按理说非递归就够用,但是这里也可以利用传入指针引用的特性来进行说明:

递归实现:

        大逻辑还是如上,只不过不用找父节点了,如果为空,直接构造节点插入即可。(指针引用,传入的就是原本的本身)。大于递归右子树,小于递归左子树,相等返回false:

        因为,在插入的时候只会传入一个key参数,递归的话是需要节点指针的,所以可以考虑子函数进行递归实现:

		bool insertR(const K& k)
		{
			return _insertR(_root, k);
		}
        // ......
    private:
        // 递归插入子函数
		bool _insertR(Node*& node, const K& k)
		{
			if (node == nullptr)
			{
				node = new Node(k);
				return true;
			}
			if (node->key > k) return _insertR(node->left, k);
			else if (node->key < k) return _insertR(node->right, k);
			else return false;  // 相同插入失败
		}

3.寻找

        对于寻找来说的话,同样利用搜索二叉树的特性,相等就返回true,小于走左子树,大于走右子树,同样的递归和非递归实现:

非递归实现:

		// 查找 - 是否存在key值,存在返回true
		bool find(const K& k)
		{
			Node* c = _root;
			while (c)
			{
				if (c->key > k) c = c->left;
				else if (c->key < k) c = c->right;
				else return true;
			}
			return false;
		}

递归实现:

		bool findR(const K& k)
		{
			return _findR(_root, k);
		}
        // ......
    private:
    	// 递归寻找子函数
		bool _findR(Node* node, const K& k)
		{
			if (node == nullptr) return false;
			if (node->key > k) return _findR(node->left, k);
			else if (node->key < k) return _findR(node->right, k);
			else return true;
		}

4.删除

        对于二叉搜索树来说,删除的逻辑较为复杂。因为一个结点下有左右孩子,并且删除后需要保证二叉搜索树的逻辑不被遭到破坏,所以一般分为如下的几种删除情况:

        对于如下树来说,删除2这个结点:

1.结点无左右孩子

         直接删除即可。

2.结点有一个孩子

         只有左子树。继承2的位置。

         只有右子树。继承2的位置。

3.结点有两个孩子

        如上,找2结点右子树的最左结点(右子树最小),替换2结点,然后被替换结点发生继承(此时无法在往左走,要么剩下右子树或者空,右子树继承即可)

 

 

 

         或者,找2结点的左子树的最右结点(左子树最小),替换2结点,然后被替换结点发生继承(此时无法在往右走,要么剩下左子树或者空,左子树继承即可)

        根据如上的图解,我们可以归类为两种情况:1.不存在两个孩子。2.存在两个孩子。因为可以发现,存在一个孩子和不存在孩子都可以通过继承法进行维持搜索二叉树的性质。1.但是由于是当前结点删除,判断左右,然后直接赋值就需要判断是否是头结点的情况,直接是头结点的话,那么就要修改root了。

        2.对于存在两个孩子的话,为什么可以找左子树的最大值或者右子树的最小值呢?因为你可以看到被删除结点在当前以其为根的子树中就是中间值,那么要把它删掉我们自然需要下一个接班人,接班人是有条件的,那就是有足够的精力维护两边的平衡。

        现在设需要删除结点为父,取父的左子树最大值或者右子树的最小值。另外需要注意的是替换后,在替换的那个节点的原本位置就需要执行1的那个过程,因为继承的话如果是左子树最大值,那么就是左子树最大值的左边,即由其父的右边继承

         但是需要注意的是,既然是其父,那么如果左子树的最大值就是父的左呢?比如:

         可以看到,如果按照原本的逻辑走,就会出现错误,原本结点的右子树大量丢失,所以因为此时父的左子树没有发生变化,那么父也就不会随着其找最大值时变化,这个时候进行判断一下。直接将父节点的左子树继承即可:

         同理,如果是找右子树的最小值相反着来即可。

非递归实现:

        首先删除逻辑同上,因为是非递归,判断循环结束条件为空即可,如果真为空,就表示找不到此结点,大于走右子树,小于走左子树。相等就进行删除操作。删除成功后就直接进行返回。删除分为上面的情况,谨慎处理细节:

		// 删除 - 非递归
		bool erase(const K& k)
		{
			Node* p = _root;
			Node* c = _root;
			while (c)
			{
				if (c->key > k)
				{
					p = c;
					c = c->left;
				}
				else if (c->key < k)
				{
					p = c;
					c = c->right;
				}
				else
				{
					// 删除分为三种情况:1.两个孩子都没有 2.有一个孩子 3.有两个孩子 12可以归为一类,进行继承操作。3使用替换法
					if (c->left == nullptr)
					{
						// 小心就是删除_root结点
						if (c == _root) _root = c->right;
						// 判断继承于哪边孩子
						else if (p->key > k) p->left = c->right;
						else p->right = c->right;
						delete c;
					}
					else if (c->right == nullptr)
					{
						if (c == _root) _root = c->left;
						else if (p->key > k) p->left = c->left;
						else p->right = c->left;
						delete c;
					}
					else
					{
						// 替换法
						Node* maxc = c->left;
						Node* maxp = c;
						// 找到左子树的最大值或者右子树的最小值即可
						while (maxc->right)
						{
							// 左子树最大值,一直往右找
							maxp = maxc;
							maxc = maxc->right;
						}
						c->key = maxc->key;  // 交换值
						//maxp->right = maxc->left;  // 后面的需要继承 无论nullptr 千万注意maxp的问题 如果maxp没有动的话,此时就会出现问题
						if (maxp == c) maxp->left = maxc->left;
						else maxp->right = maxc->left;
						delete maxc;
					}
					// 删除成功 -- 找的到目标key值
					return true;
				}
			}
			return false;
		}

 递归实现:

        由于递归删除有着可以传入指针引用的特点,所以在第一种的情况下直接继承的话直接给递归来的指针对应的继承指针即可。此时就不用怕是否为根结点了。因为即使是根节点,此时修改的也就是根节点。

        对于两个孩子都存在的情况下,首先找替换不变,直接走循环即可。继承的话我们可以不用之前那么复杂的分析,可以就从当前被删结点的左子树(或者右子树开始),删除被替换结点即可。此时走的一定是上面的继承逻辑。完美展现了复用的好处。

		bool eraseR(const K& k)
		{
			return _eraseR(_root, k);
		}
	private:
    	// 递归删除子函数
		bool _eraseR(Node*& node, const K& k)
		{
			if (node == nullptr) return false;  // 没有找到,无法删除
			if (node->key > k) return _eraseR(node->left, k);
			else if (node->key < k) return _eraseR(node->right, k);
			else
			{
				Node* tmp = node;  // 记录当前指针,方便释放空间
				// 找到了,进行删除操作
				// 第一种情况 存在一个孩子或者没有孩子
				if (node->left == nullptr)
				{
					node = node->right;
				}
				else if (node->right == nullptr)
				{
					node = node->left;
				}
				else
				{
					// 第二种情况 两个孩子存在
					Node* maxc = node->left;  // 找左子树的最大值
					while (maxc->right)
					{
						maxc = maxc->right;
					}
					node->key = maxc->key;
					// 交换值之后,不再进行复杂的分析如何接后面的情况,继续递归给别人,将此交换点干掉即可
					return _eraseR(node->left, maxc->key);  // 必须从左树开始找,不能直接传maxc,否则只是修改形参里的值,传回了释放的空间就成野指针了
				}
				delete tmp;
				return true;
			}
		}

5.遍历

        相对于之前的代码实现,遍历就简单太多了,直接中序遍历即可:使用递归即可

		// 中序遍历 - 递归
		void order()
		{
			_order(_root);
			cout << endl;
		}
        // ......
    private:
    	// 中序遍历递归
		void _order(Node* node)
		{
			if (node == nullptr) return;
			_order(node->left);
			cout << node->key << " ";
			_order(node->right);
		}

6.综合代码

        综上,对于key模型的二叉搜索树我们的总体实现代码如下:

// 二叉搜索树 - key&&key/value 版本 满足每个子树(包括整个树)左子树小于结点,结点小于右子树 (均针对的树上的所有值)

namespace Key
{
	template<class K>
	struct BStreeNode  // 二叉树节点
	{
		BStreeNode* left;
		BStreeNode* right;
		K key;
		BStreeNode(const K& k)
			:key(k)
			,left(nullptr)
			,right(nullptr)
		{}
	};

	template<class K>
	class BStree
	{
		typedef BStreeNode<K> Node;
	public:
		BStree() = default;   // C++11 即使写其他构造函数,也会生成默认构造函数
		// 拷贝构造
		BStree(const BStree<K>& b)
		{
			_BStree(_root, b._root);
		}
		~BStree()
		{
			_delBStree(_root);
		}
		// 赋值重载 当一个资本家,让别人帮自己工作
		BStree& operator=(BStree<K> b)
		{
			swap(b);
			return *this;
		}
		// 内部交换属性函数
		void swap(BStree<K>& b)
		{
			Node* tmp = b._root;
			b._root = _root;
			_root = tmp;
		}
		// 中序遍历 - 递归
		void order()
		{
			_order(_root);
			cout << endl;
		}
		 非递归-插入-寻找-删除///
		// 插入 - 非递归
		bool insert(const K& k)
		{
			Node* n = new Node(k);

			Node* p = _root;
			Node* c = _root;
			while (c)
			{
				if (c->key > k)
				{
					p = c;
					c = c->left;
				}
				else if (c->key < k)
				{
					p = c;
					c = c->right;
				}
				else
					return false;  // 相等
			}
			if (p == nullptr) _root = n;
			else if (p->key > k) p->left = n;
			else p->right = n;
			return true;
		}
		// 查找 - 是否存在key值,存在返回true
		bool find(const K& k)
		{
			Node* c = _root;
			while (c)
			{
				if (c->key > k) c = c->left;
				else if (c->key < k) c = c->right;
				else return true;
			}
			return false;
		}
		// 删除 - 非递归
		bool erase(const K& k)
		{
			Node* p = _root;
			Node* c = _root;
			while (c)
			{
				if (c->key > k)
				{
					p = c;
					c = c->left;
				}
				else if (c->key < k)
				{
					p = c;
					c = c->right;
				}
				else
				{
					// 删除分为三种情况:1.两个孩子都没有 2.有一个孩子 3.有两个孩子 12可以归为一类,进行继承操作。3使用替换法
					if (c->left == nullptr)
					{
						// 小心就是删除_root结点
						if (c == _root) _root = c->right;
						// 判断继承于哪边孩子
						else if (p->key > k) p->left = c->right;
						else p->right = c->right;
						delete c;
					}
					else if (c->right == nullptr)
					{
						if (c == _root) _root = c->left;
						else if (p->key > k) p->left = c->left;
						else p->right = c->left;
						delete c;
					}
					else
					{
						// 替换法
						Node* maxc = c->left;
						Node* maxp = c;
						// 找到左子树的最大值或者右子树的最小值即可
						while (maxc->right)
						{
							// 左子树最大值,一直往右找
							maxp = maxc;
							maxc = maxc->right;
						}
						c->key = maxc->key;  // 交换值
						//maxp->right = maxc->left;  // 后面的需要继承 无论nullptr 千万注意maxp的问题 如果maxp没有动的话,此时就会出现问题
						if (maxp == c) maxp->left = maxc->left;
						else maxp->right = maxc->left;
						delete maxc;
					}
					// 删除成功 -- 找的到目标key值
					return true;
				}
			}
			return false;
		}
		 递归-插入-寻找-删除///
		bool insertR(const K& k)
		{
			return _insertR(_root, k);
		}
		bool findR(const K& k)
		{
			return _findR(_root, k);
		}
		bool eraseR(const K& k)
		{
			return _eraseR(_root, k);
		}
	private:
		// 递归删除子函数
		bool _eraseR(Node*& node, const K& k)
		{
			if (node == nullptr) return false;  // 没有找到,无法删除
			if (node->key > k) return _eraseR(node->left, k);
			else if (node->key < k) return _eraseR(node->right, k);
			else
			{
				Node* tmp = node;  // 记录当前指针,方便释放空间
				// 找到了,进行删除操作
				// 第一种情况 存在一个孩子或者没有孩子
				if (node->left == nullptr)
				{
					node = node->right;
				}
				else if (node->right == nullptr)
				{
					node = node->left;
				}
				else
				{
					// 第二种情况 两个孩子存在
					Node* maxc = node->left;  // 找左子树的最大值
					while (maxc->right)
					{
						maxc = maxc->right;
					}
					node->key = maxc->key;
					// 交换值之后,不再进行复杂的分析如何接后面的情况,继续递归给别人,将此交换点干掉即可
					return _eraseR(node->left, maxc->key);  // 必须从左树开始找,不能直接传maxc,否则只是修改形参里的值,传回了释放的空间就成野指针了
				}
				delete tmp;
				return true;
			}
		}
		// 递归寻找子函数
		bool _findR(Node* node, const K& k)
		{
			if (node == nullptr) return false;
			if (node->key > k) return _findR(node->left, k);
			else if (node->key < k) return _findR(node->right, k);
			else return true;
		}
		// 递归插入子函数
		bool _insertR(Node*& node, const K& k)
		{
			if (node == nullptr)
			{
				node = new Node(k);
				return true;
			}
			if (node->key > k) return _insertR(node->left, k);
			else if (node->key < k) return _insertR(node->right, k);
			else return false;  // 相同插入失败
		}
		// 递归清理资源
		void _delBStree(Node*& node)
		{
			if (node == nullptr) return;

			_delBStree(node->left);
			_delBStree(node->right);
			delete node;
			node = nullptr;
		}
		// 拷贝构造递归
		void _BStree(Node*& node1, const Node* node2)
		{
			if (node2 == nullptr) return;
			node1 = new Node(node2->key);
			_BStree(node1->left, node2->left);
			_BStree(node1->right, node2->right);
		}
		// 中序遍历递归
		void _order(Node* node)
		{
			if (node == nullptr) return;
			_order(node->left);
			cout << node->key << " ";
			_order(node->right);
		}

		Node* _root = nullptr;  // 这样使用默认的构造函数即可
	};
}

        综上就是key模型的二叉搜索树的实现啦~~,key-value就直接增加一个成员即可,构造时传上其参数即可,其余均不变,因为仍然用key进行比较的。代码仅供参考,还请大佬多多指正!

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

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

相关文章

数据库基本操作

目录 数据库操作 创建数据库 查看数据库 选择数据库 删除数据库 注释 数据表操作 创建数据表 查看数据表 查看数据表 查看数据表的相关信息 修改数据表 修改数据表名称 修改表选项 查看表结构 查看数据表的字段信息 查看数据表的创建信息 查看数据表结构 修…

linux进程间通信之共享内存

目录 一&#xff0c;共享内存原理 二&#xff0c;创建共享内存 1&#xff0c;shmget创建共享内存 2&#xff0c;shmat挂接共享内存 3&#xff0c;shmdt取消挂接共享内存 4&#xff0c;shmctl删除共享内存 三&#xff0c;代码使用 1,com.hpp 2&#xff0c;ipc_client.c…

Allegro基本规则设置指导书之Physical Region

Allegro基本规则设置指导书之Physical Region 下面介绍基本规则设置指导书之Physical Region 空白的地方创建一个Region 给新建的Region匹配一个规则,所有区域里面的Physical相关的都按照Region的规则来 当部分网络想按照本身的规则来匹配,可以创建region-Class 然后匹配…

目标检测算法——医学图像开源数据集汇总(附下载链接)

关注”PandaCVer“公众号 深度学习Tricks&#xff0c;第一时间送达 目录 1.血细胞图像数据 2.眼病深度学习数据集 3.皮肤病数据集 4.膝关节 X 射线图像数据集 小海带整理不易&#xff0c;小伙伴们记得一键三连喔&#xff01;&#xff01;&#xff01; >>>一起交流…

VisualSVN 是 Visual Studio 的专业级 Subversion 集成插件

用于 Visual Studio 的 VisualSVN 专业且无缝的 Subversion 集成。 专业级 Subversion 集成 VisualSVN 是 Visual Studio 的专业级 Subversion 集成插件。 VisualSVN 的主要优点是&#xff1a; 无与伦比的可靠性&#xff1a; Visual Studio 永远不会因为 VisualSVN 而崩溃或挂…

保护鲸鱼动物网页设计作业 静态HTML宠物主题网页作业 DW鲸鱼网站模板下载 大学生简单动物网页作品代码 个人网页制作 学生个人网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

2022阿里云栖大会,顶尖科技趋势峰会和全链路元宇宙体验

2022年的11月3日-5日是阿里巴巴云栖大会的日子&#xff0c;地点在云栖小镇&#xff0c;本人有幸报名参加了5日那场&#xff0c;因为5日是周六。秉着打工人工作日需要搬砖&#xff0c;因为“公司离不开我”&#xff0c;哈哈哈&#xff0c;实际上是每天满满的工作量。所以只能选择…

一文彻底搞懂协程(coroutine)是什么,值得收藏

什么是协程 我们可以简单的认为&#xff1a;协程就是用户态的线程&#xff0c;但是上下文切换的时机是靠调用方&#xff08;写代码的开发人员&#xff09;自身去控制的。 同时&#xff0c;协程和用户态线程非常接近&#xff0c;用户态线程之间的切换不需要陷入内核&#xff0…

NYIST(计科ACMTC)第三次招新赛题解

A题 原文, 原比赛B题 牛客练习赛104【出题人题解】 - 知乎 直接输出 输入的数 就可以了 B题 C题 找到分别处理"无留陀的化身"坐标轴的x轴和y轴, 组合成无留陀的坐标, 再遍历求纳西妲的坐标, 相减即可 /* ⣿⣿⣿⣿⣿⣿⡷⣯⢿⣿⣷⣻⢯⣿⡽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿…

ROS小工具学习与使用

ROS小工具学习与使用 rqt的使用 rqt_bag工具 rqt_bag <your bagfile> #使用rqt_bag查看你的rosbag例如&#xff1a;可以查看第一帧GPS的rawdata信息&#xff0c;如下图&#xff1a; 参考文献&#xff1a; 1、http://wiki.ros.org/rqt_bag 2、rosbag与rqt_bag的常用 rq…

Nacos学习笔记

视频学习指路&#xff1a; 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 Nacos nacos注册中心的搭建 1.下载nacos的安装包&#xff0c;github地址&#xff1a;https://github.com/alibaba/nacos&…

Tkinter保姆级教程(上)

目录 什么是GUI Tkinter用法详解 第一个Tkinter程序 常用控件和属性 主窗口 Label标签控件 Button按钮控件 Entry输入控件 基本属性 Text 文本控件 列表框(ListBox)和组合框(Combobox) 单选框(Radiobutton)和多选框按钮(Checkbutton) 什么是GUI 图形用户界面&#x…

运算放大器正反馈负反馈判别法

---------------------------------------------------------------------------------------------------------------- 反馈可分为负反馈和正反馈。前者使输出起到与输入相反的作用&#xff0c;使系统输出与系统目标的误差减小&#xff0c;系统趋于稳定&#xff1b;后者使输出…

java面向对象(上)

一、java面向对象学习的三条主线1.java类以及类的成员&#xff1a;属性、方法、构造器&#xff1b;代码块、内部类。2.面向对象的三大特征&#xff1a;封装性&#xff0c;继承性&#xff0c;多态性&#xff0c;&#xff08;抽象性&#xff09;。3.其他关键字&#xff1a;this&a…

.net技术第一章

文章目录.NETC# (C Sharp)的特点C# 的应用范围.NET Framework1.2 创建简单的C#程序结构和书写规则类型的声明和使用类型的声明和使用命名空间使用方法命名空间举例注释Main方法命令行参数Main返回值控制台输入和输出例子格式化.NET 由微软公司提供的免费、跨平台的开源通用开发…

复杂分数 马蹄集

复杂分数 难度&#xff1a;白银 0时间限制&#xff1a;1秒 巴占用内存&#xff1a;64M 编写程序连续输入a1、a2、、a5,计算下列表达式的值并输出。本题不考虑输 入0&#xff0c;负数或者其他特殊情况。 1十1中 al 3 4 格式 输入格式&#xff1a;输入整型&#xff0c;空格分隔。…

Go语言学习(二) 函数

文章目录函数go函数基本语法go不支持重载go中支持可变参数函数 go函数基本语法 先来看看go中函数的基本使用 package mainimport "fmt"/* func 函数名(形参列表) (返回值类型列表) {执行语句..return 返回值列表 } */ //自定义函数&#xff1a;功能&#xff1a;两…

实验三:多种影响因素下购房方案的比较

根据呼文军[1]等建立的购房决策数学模型式(1)[1]&#xff0c;通过对影响购房的多个因素进行科学地分析、比较&#xff0c;从若干备选购房方案中做出最佳的选择。 QP*WT &#xff08;1&#xff09; 在文章的“实例分析”中&#xff0c;假设…

Kubeadm搭建kubernetes(k8s)集群

目录 一、集群介绍 1、集群搭建方法 二、集群部署 环境配置 所有节点&#xff0c;关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换 node02&#xff08;192.168.137.30&#xff09; node01&#xff08;192.168.137.20&#xff09; ​编辑 master&#…

单链表经典例题

LeetCode题解移除链表元素反转链表合并两个有序链表移除链表元素 题目描述&#xff1a; ➡️挑战链接⬅️、 分析&#xff1a; 该题是要求我们删除指定元素&#xff0c;那么我们就定义一个cur指针去遍历整个链表就好了&#xff0c;每当我们遇到cur->valval;等于特定值的时…