C++数据结构重要知识点(4)(map和set封装)

news2024/11/13 7:53:12

前面我们已经实现了红黑树,接下来我们需要将这个数据结构封装成map和set两个容器,其中很多地方的处理都有一定难度,下面会按照我的思路讲解map的改造

map成员变量如下,如果是第一次看到它,那么一定会很陌生,下面我会从问到答的形式分析:

1.为什么使用pair<K, V>?

一颗红黑树要实例化出set和map,其中set和map都有key,但set存储数据的对象还是key,map存储数据的对象是pair,这里为了统一map和set,统一将第一个参数用于接收key,第二个参数用于接收存储数据的类型。第三个参数后面紧接着会讲,它也是为了统一map和set所需的参数。

2.为什么不用更简单的K, V来直接实例化红黑树?

在红黑树的实现中,我们直接将树存储的数据类型写死了,但在这里我们要实例化的是两个容器,两个容器存储数据的方式不同,所以我们要抓住它们的共同点:都有key,都有一个存储数据的类型。如果直接以K, V来存储数据,我们可以想象在RBtree实现中没有办法来区分map和set。V传map的value,set的key,那如何判断这个V到底是map的value还是set的key呢?

理解了这个,set的成员变量的实例化就能很快写出来了。

3.为什么要使用const K和pair<const K, V>呢?那个const有什么讲究吗?

上面我讲了,K是接收key,而T是接收存储类型。T接收存储类型后会用这个类型来实例化结点

在之前红黑树的实现里,我们默认创建的node都是pair,但在这里我们要区分set和map,用模板参数T来作为_val的类型。当set和map实例化时,会分别传const K和pair<const K, V>作为_val的类型用于区分。同时,当利用迭代器对数据进行访问时,得到的_val也是const K和pair<const K, V>类型,均不能对key进行修改,符合了关联式容器的需求。

3.改变了模板参数会对原来红黑树的实现产生怎样的影响?

首先我们需要想清楚,结点指针始终不变,都有_left、_right、_parent,但是_val类型发生改变,这意味着只有涉及_val比较的代码才需要修改。像旋转、红黑树的判断的函数都只涉及结点指针或颜色的识别,跟值没关系,所以它们不需要改变。

(1)_val比较

对于map的pair,比较的是pair的key,而对于set,比较的也是key。但是如何区分呢?

很多人会想到第一个模板参数传的就是key,但注意在RBTree中,第一个模板参数并没有被实例化,结点中也没有key,没办法利用,所以这个时候我们使用了第三个模板参数——key_of_T。

这其实就是个仿函数,是在map和set的类中实现,在实例化时将对应的函数名作为第三个参数传过去,当调用的时候就针对不同的函数得到相对应的key。

注意map_key_of_T不是模板函数,它是在map这个模板函数实例化后自动实例化的类,传参时直接将名字传过去即可。

4.iterator的实现

我们先暂时认为Node的指针就能完成迭代器,如下图,注意当声明模板类的成员变量类型时,最前面要加typename防止被认为是静态成员变量导致编译不通过。

实例化的时候要根据迭代器的成员变量实例化所需的类型来确定迭代器的模板参数,要会反推,明白每个参数出现的意义。

常规的迭代器操作这里不再详述

下面重点讲讲operator++

当我们传begin()时得到的是最左边结点的迭代器,之后每一次++都要按照左中右的规则来遍历,如何控制呢?很明显不能再使用以前的递归方法了,递归方法是遍历,即从头到尾走一遍,递归没有办法从中间某个结点开始向后走,也没办法在中途停下来。

我们需要根据中序的路线再找一种新的规律

我们发现,it所在的位置都可以看作所在子树的根节点,根据左中右的规则,每次it++都是访问这个右树的最左结点,如2->3,4->5。但是这里还有个疑问,1->2,3->4遵循什么规律呢?5之后怎么处理呢?

我们发现刚刚的规律是it所在的位置都可以看作所在子树的根节点,在这种思想下默认左和中都已经遍历完了,只需要去找右的最左结点即可,如果像1和3那样没有右,就意味着当前整棵树都访问完了。关键的点在于,这颗被访问完的子树如果是上一个父节点的左子树,我们就需要找到这个父节点然后返回;如果这颗被访问完的子树如果是上一个父节点的右子树,根据左中右,父节点所在的树也访问完了。所以,1和3这种情况就是要去找一个父节点,使得对于这个父节点而言,上个节点是在它的左子树,如1->2,3->4。如果找不到呢?如5,我们发现当它通过_root->parent走到nullptr也没有找到符合规则的父节点时,就退出,表示整棵树都走完了。

只要理解了上面的思想方法,代码就很简单了,总体思路同样是nodeP向上探路,nodeC在后面跟着,nodeC如果是nodeP的左,就意味找到了父节点

operator--的方法一样,但是有个细节需要处理

当我们传end()时,是尾节点的下一个,即nullptr。那么如何让end()--后能找到尾节点呢?

注意分析为什么存在这个问题,my_RBTree::map<string, int>::iterator it = m.end(),it是一个类对象,其成员变量只有一个Node* _cur == nullptr,此外迭代器类里只有m对应的实例化类型,但没有m这个对象相关的任何指针或引用,这个时候--什么也做不了。因此我们需要在迭代器里专门再加一个成员变量。

有且仅有在这种处理下,我们才能进行特殊处理

rightmost和leftmost就是寻找最右和最左结点,很简单,但调用非静态成员函数需要实例化对象,这也是为什么要传一个指针过去的原因之一。

iterator实现后,insert也需要修改,我们要在两个层面进行修改,红黑树的insert要返回pair,其key为插入结点的指针或找到的结点指针,其value是插入是否成功。在map里,就利用接收到的pair的key来构造iterator,再返回一个pair<iterator, bool>即可。

至此,map和set封装所有难点都讲解完了,有很多小细节需要多上手练习解决。

5.所有代码

map.hpp

#pragma once


#include "RBTree.hpp"
using namespace my_RBTree;


namespace my_RBTree
{
	template<class K, class T, class Key_Of_T>
	struct map_iterator
	{
		map_iterator(my_RBTree::RBTree<K, T, Key_Of_T>* t, typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* cur = nullptr)
			:_t(t),
			_cur(cur)
		{}

		map_iterator& operator++()
		{
			typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			nodeC = nodeC->_right;
			if (nodeC)
			{
				while (nodeC->_left)
					nodeC = nodeC->_left;
				_cur = nodeC;
				return *this;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_left == nodeC)
				{
					_cur = nodeP;
					return *this;
				}
			}

			return *this;
		}


		map_iterator operator++(int)
		{
			typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			map_iterator ret = *this;

			nodeC = nodeC->_right;
			if (nodeC)
			{
				while (nodeC->_left)
					nodeC = nodeC->_left;
				_cur = nodeC;
				return ret;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_left == nodeC)
				{
					_cur = nodeP;
					return ret;
				}
			}

			return ret;
		}


		map_iterator& operator--()
		{
			typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			if (_cur == nullptr)
			{
				_cur = _t->rightmost();
				return *this;
			}

			nodeC = nodeC->_left;
			if (nodeC)
			{
				while (nodeC->_right)
					nodeC = nodeC->_right;
				_cur = nodeC;
				return *this;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_right == nodeC)
				{
					_cur = nodeP;
					return *this;
				}
			}

			return *this;
		}

		map_iterator operator--(int)
		{
			typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			map_iterator ret = *this;

			if (_cur == nullptr)
			{
				_cur = _t->rightmost();
				return ret;
			}

			nodeC = nodeC->_left;
			if (nodeC)
			{
				while (nodeC->_right)
					nodeC = nodeC->_right;
				_cur = nodeC;
				return ret;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_right == nodeC)
				{
					_cur = nodeP;
					return ret;
				}
			}

			return ret;
		}

		T& operator*()
		{
			return _cur->_val;
		}

		T* operator->()
		{
			return &(_cur->_val);
		}


		bool operator==(const map_iterator& it)
		{
			return it._cur == _cur;
		}

		bool operator!=(const map_iterator& it)
		{
			return it._cur != _cur;
		}

		typename my_RBTree::RBTree<K, T, Key_Of_T>::Node* _cur;

		my_RBTree::RBTree<K, T, Key_Of_T>* _t;
	};

	template<class K, class V>
	class map
	{
	public:
		struct map_key_of_T;
		typedef map_iterator<K, pair<const K, V>, map_key_of_T> iterator;
		typedef map_iterator<K, pair<const K, const V>, map_key_of_T> const_iterator;

		iterator begin()
		{
			return iterator(&_tree, _tree.leftmost());
		}

		iterator end()
		{
			return iterator(&_tree);
		}

		pair<iterator, bool> insert(const K& key, const V& val)
		{
			pair<typename my_RBTree::RBTree<K, pair<const K, V>, map_key_of_T>::Node*, bool> p = _tree.insert({ key, val });
			return { iterator(&_tree, p.first), p.second };
		}

		bool isRBTree()
		{
			return _tree.isRBTree();
		}

	private:

		struct map_key_of_T
		{
			const K& operator()(typename my_RBTree::RBTree<K, pair<const K, V>, map_key_of_T>::Node* cur)
			{
				return cur->_val.first;
			}
		};

	private:
		RBTree<K, pair<const K, V>, map_key_of_T> _tree;
	};
}

set.hpp

#pragma once


#include "RBTree.hpp"


namespace my_RBTree
{

	template<class K, class Key_Of_T>
	struct set_iterator
	{
		set_iterator(my_RBTree::RBTree<K, const K, Key_Of_T>* t, typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* cur = nullptr)
			:_t(t),
			_cur(cur)
		{}

		set_iterator& operator++()
		{
			typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			nodeC = nodeC->_right;
			if (nodeC)
			{
				while (nodeC->_left)
					nodeC = nodeC->_left;
				_cur = nodeC;
				return *this;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_left == nodeC)
				{
					_cur = nodeP;
					return *this;
				}				
			}

			return *this;
		}

		set_iterator operator++(int)
		{
			typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			set_iterator ret = *this;

			nodeC = nodeC->_right;
			if (nodeC)
			{
				while (nodeC->_left)
					nodeC = nodeC->_left;
				_cur = nodeC;
				return ret;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_left == nodeC)
				{
					_cur = nodeP;
					return ret;
				}
			}

			return ret;
		}

		set_iterator& operator--()
		{
			typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			if (_cur == nullptr)
			{
				_cur = _t->rightmost();
				return *this;
			}

			nodeC = nodeC->_left;
			if (nodeC)
			{
				while (nodeC->_right)
					nodeC = nodeC->_right;
				_cur = nodeC;
				return *this;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_right == nodeC)
				{
					_cur = nodeP;
					return *this;
				}
			}

			return *this;
		}

		set_iterator operator--(int)
		{
			typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* nodeC = _cur, * nodeP = _cur;

			set_iterator ret = *this;

			if (_cur == nullptr)
			{
				_cur = _t->rightmost();
				return ret;
			}

			nodeC = nodeC->_left;
			if (nodeC)
			{
				while (nodeC->_right)
					nodeC = nodeC->_right;
				_cur = nodeC;
				return ret;
			}

			while (nodeP)
			{
				nodeC = nodeP, nodeP = nodeP->_parent;
				if (nodeP == nullptr || nodeP->_right == nodeC)
				{
					_cur = nodeP;
					return ret;
				}
			}

			return ret;
		}


		const K& operator*()
		{
			return _cur->_val;
		}

		const K* operator->()
		{
			return &(_cur->_val);
		}

		bool operator==(const set_iterator& it)
		{
			return it._cur == _cur;
		}		
		
		bool operator!=(const set_iterator& it)
		{
			return it._cur != _cur;
		}

		typename my_RBTree::RBTree<K, const K, Key_Of_T>::Node* _cur;
		my_RBTree::RBTree<K, const K, Key_Of_T>* _t;
	};


	template<class K>
	class set
	{
	public:
		struct set_key_of_T;
		typedef set_iterator<K, set_key_of_T> iterator;
		typedef set_iterator<K, set_key_of_T> const_iterator;

		iterator begin()
		{
			return iterator(&_tree, _tree.leftmost());
		}		
		
		iterator end()
		{
			return iterator(&_tree);
		}

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename my_RBTree::RBTree<K, const K, set_key_of_T>::Node*, bool> p = _tree.insert(key);
			return { iterator(&_tree, p.first), p.second };
		}

		bool isRBTree()
		{
			return _tree.isRBTree();
		}

	private:

		struct set_key_of_T
		{
			const K& operator()(typename my_RBTree::RBTree<K, const K, set_key_of_T>::Node* cur)
			{
				return cur->_val;
			}
		};
	private:
		RBTree<K, const K, set_key_of_T> _tree;
	};


}

RBTree.hpp

#pragma once


#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <time.h>

using namespace std;


namespace my_RBTree
{
	enum Color
	{
		BLACK,
		RED
	};

	template<class K, class T>
	struct RBTreeNode
	{
		RBTreeNode(const T& val, Color col)
			:_col(col)
			,_val(val)
			,_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
		{}

		Color _col;
		T _val;
		RBTreeNode<K, T>* _left;
		RBTreeNode<K, T>* _right;
		RBTreeNode<K, T>* _parent;
	};


	template<class K, class T, class Key_Of_T>
	class RBTree
	{
	public:
		typedef RBTreeNode<K, T> Node;

		Node* leftmost()
		{
			Node* cur = _root;
			while (cur && cur->_left)
				cur = cur->_left;
			return cur;
		}

		Node* rightmost()
		{
			Node* cur = _root;
			while (cur && cur->_right)
				cur = cur->_right;
			return cur;
		}

		void RotateR(Node* nodeG)
		{
			Node* nodeP = nodeG->_left, * nodeGP = nodeG->_parent, * nodeLR = nodeP->_right;

			nodeP->_right = nodeG, nodeP->_parent = nodeGP;
			if (nodeGP)
			{
				if (nodeGP->_left == nodeG)
					nodeGP->_left = nodeP;
				else
					nodeGP->_right = nodeP;
			}
			else
				_root = nodeP;
			
			nodeG->_parent = nodeP, nodeG->_left = nodeLR;
			if (nodeLR)
				nodeLR->_parent = nodeG;
		}

		void RotateL(Node* nodeG)
		{
			Node* nodeP = nodeG->_right, * nodeGP = nodeG->_parent, * nodeRL = nodeP->_left;

			nodeP->_left = nodeG, nodeP->_parent = nodeGP;
			if (nodeGP)
			{
				if (nodeGP->_left == nodeG)
					nodeGP->_left = nodeP;
				else
					nodeGP->_right = nodeP;
			}
			else
				_root = nodeP;

			nodeG->_parent = nodeP, nodeG->_right = nodeRL;
			if (nodeRL)
				nodeRL->_parent = nodeG;
		}

		pair<Node*, bool> insert(const T& val)
		{
			Node* nodeRet = nullptr;

			if (_root == nullptr)
			{
				_root = new Node(val, BLACK);
				return { _root, true };
			}

			Node* nodeC = _root, * nodeP = nullptr, * nodeN = new Node(val, RED);
			while (nodeC)
			{
				if (Key_Of_T()(nodeC) == Key_Of_T()(nodeN))
					return { nodeC, false };


				if (Key_Of_T()(nodeC) < Key_Of_T()(nodeN))
					nodeP = nodeC, nodeC = nodeC->_right;
				else
					nodeP = nodeC, nodeC = nodeC->_left;
			}

			if (Key_Of_T()(nodeP) < Key_Of_T()(nodeN))
			{
				nodeP->_right = new Node(val, RED);
				nodeC = nodeP->_right;
				nodeRet = nodeC;
				nodeC->_parent = nodeP;
			}
			else
			{
				nodeP->_left = new Node(val, RED);
				nodeC = nodeP->_left;
				nodeRet = nodeC;
				nodeC->_parent = nodeP;
			}

			Node* nodeG = nodeP->_parent;
			while (nodeP && nodeG)
			{
				if (nodeP->_col == BLACK)
					break;

				Node* nodeU = nullptr;
				if (nodeG->_left == nodeP)
					nodeU = nodeG->_right;
				else
					nodeU = nodeG->_left;

				//变色
				if (nodeU && nodeP->_col == RED && nodeU->_col == RED)
				{
					nodeP->_col = nodeU->_col = BLACK;
					if (nodeG != _root)
						nodeG->_col = RED;
					else
						nodeG->_col = BLACK;

					nodeC = nodeG, nodeP = nodeC->_parent;
					if (nodeP)
						nodeG = nodeP->_parent;
					else
						nodeG = nullptr;
				}
				
				//旋转,只涉及nodeG,nodeP,nodeC
				else
				{
					if (nodeG->_left == nodeP)
					{
						if (nodeP->_left == nodeC)
						{
							RotateR(nodeG);
							nodeP->_col = BLACK, nodeG->_col = RED;
						}
						else
						{
							RotateL(nodeP), RotateR(nodeG);
							nodeC->_col = BLACK, nodeG->_col = RED;
						}
					}
					else
					{
						if (nodeP->_right == nodeC)
						{
							RotateL(nodeG);
							nodeP->_col = BLACK, nodeG->_col = RED;
						}
						else
						{
							RotateR(nodeP), RotateL(nodeG);
							nodeC->_col = BLACK, nodeG->_col = RED;
						}
					}

					break;
				}

			}

	
			return { nodeRet, true };
		}


		bool isRBTree()
		{
			bool ret1 = _Contiguous_RNode(_root);
			bool ret2 = _Count_BNode(_root, 0, _Path_BNode(_root));

			if (ret1)
				cout << endl << "结点颜色均符合规则,根节点为黑且没有连续红节点!" << endl;
			else
				cout << endl << "颜色出现故障,请检查!" << endl;

			if (ret2)
				cout << endl << "每条路径的黑色节点数相同!" << endl;
			else
				cout << endl << "某条路径黑色节点数出现问题,请检查!" << endl;

			if (ret1 && ret2)
			{
				cout << endl << "经检查,本树为红黑树!" << endl;
				return true;
			}

			cout << endl << "经检查,本树不为红黑树!" << endl;
			return false;

		}

	private:
		bool _Contiguous_RNode(Node* cur_root)
		{
			if (cur_root == nullptr)
				return true;

			if (cur_root == _root && cur_root->_col == RED)
			{
				cout << "根节点为红色!" << endl;
				return false;
			}

			if (cur_root->_col == RED && cur_root->_parent->_col == RED)
				return false;

			return _Contiguous_RNode(cur_root->_left) && _Contiguous_RNode(cur_root->_right);
		}

		bool _Count_BNode(Node* cur_root, int count, int num_of_black_node)
		{
			if (cur_root == nullptr)
			{
				if (count == num_of_black_node)
					return true;
				return false;
			}

			if (cur_root->_col == BLACK)
				count++;

			return _Count_BNode(cur_root->_left, count, num_of_black_node) && _Count_BNode(cur_root->_right, count, num_of_black_node);
		}

		int _Path_BNode(Node* cur_root)
		{
			if (cur_root == nullptr)
				return 0;

			return (cur_root->_col == BLACK ? 1 : 0) + _Path_BNode(cur_root->_left);
		}

	private:
		Node* _root = nullptr;
	};

}

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

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

相关文章

C高级(学习)2024.8.1

目录 shell命令 数组 数组的赋值 数组的调用 遍历数组 函数 函数的定义方式 函数调用 分文件编程 源文件 头文件 include引用时“”和<>的区别 编译工具 gcc编译工具 gdb调试 make工具 定义 Makefile格式 Makefile管理多个文件 Makefile变量 自定义…

发布NPM包详细流程

制作 首先需要制作一个npm包。 按照以下步骤依次执行。 mkdir my-npm-package cd my-npm-package npm init 相信这一步不需要过多的解释&#xff0c;就是创建了一个文件夹&#xff0c;然后初始化了一下文件夹。 然后在生成的package.json文件夹中更改一下自己的配置&…

Python-docx,修改word编辑时间总计、创建时间、上次修改时间、作者、上次修改者、备注

Python版本3.9&#xff0c;Python-docx版本1.1.2 修改下图中红框内的信息 创建时间、上次修改时间、作者、上次修改者、备注&#xff0c;这些都有接口&#xff0c;调用 import docx from docx import Document from docx.oxml.ns import qn from docx.shared import Inches, …

“2028年互联网上所有高质量文本数据将被使用完毕”

研究公司Epoch AI预测&#xff0c;到2028年互联网上所有高质量的文本数据都将被使用完毕&#xff0c;机器学习数据集可能会在2026年前耗尽所有“高质量语言数据”。研究人员指出&#xff0c;用人工智能(AI)生成的数据集训练未来几代机器学习模型可能会导致“模型崩溃”&#xf…

助力外卖霸王餐系统运营 微客云近期更新汇总

全面助力霸王餐合作运营&#xff0c;给大家汇报下最近微客云更新的内容&#xff0c;说实话近期非常的忙&#xff0c;各种功能上线&#xff0c;各种市场部们反馈的需求&#xff0c;微客云霸王餐招商体系&#xff08;分站&#xff09;自年底上线到现在&#xff0c;不知已更新了多…

2024年8月初AI大赛盛宴来袭!7场赛事等你挑战,最高奖金高达1.4万!

本期为大家带来7场精彩的AI大赛&#xff0c;主要以AI绘画大赛为主打&#xff0c;涵盖1场视频大赛和1场大模型大赛。 其中&#xff0c;以下3场大赛不容错过&#xff0c;分别是“36氪AI PARTNER2024具身智能大会”、“2024年大学生AI艺术季”和“混元万物 LiblibAlx腾讯混元模型…

微型丝杆弯曲:工件精度下降的隐形杀手!

微型丝杆作为精密机械部件&#xff0c;‌其弯曲或变形会对使用它进行加工的工件产生直接影响。在机械加工中&#xff0c;微型丝杆弯曲是一个不容忽视的问题&#xff0c;它会对工件造成多方面的损害。 1、加工精度受损&#xff1a;弯曲会直接导致工具的实际运动轨迹与程序设计的…

从零开始学习网络安全渗透测试之基础入门篇——(五)WEB抓包技术HTTPS协议APP小程序PC应用WEB转发联动

HTTP/HTTPS抓包技术是一种用于捕获和分析网络流量的方法&#xff0c;它可以帮助开发者、测试人员和网络安全专家理解应用程序的网络行为、调试问题、分析性能和识别潜在的安全威胁。 一、抓包技术和工具 &#xff08;一&#xff09;Charles Charles 是一款流行的网络调试代…

leetcode刷题日记-括号生成

题目描述 题目解析 回溯的题目&#xff0c;不过这个两个if我就感觉有点难以理解了&#xff0c;不过仔细的思考了一下&#xff0c;确实考虑到了每个位置的情况&#xff0c;特别是针对右边括号 题目代码 class Solution:def generateParenthesis(self, n: int) -> List[str…

苹果的秘密武器:折叠屏iPhone即将来袭,可能是史上最薄折叠屏?

苹果公司一直以来以其独特的产品设计理念和卓越的技术创新能力而闻名。近期&#xff0c;有关苹果折叠屏iPhone的消息再次引发了业界的高度关注。 据可靠消息源透露&#xff0c;这款备受期待的设备已经结束了实验阶段&#xff0c;预计最早将在2026年与消费者见面。 折叠屏iPhon…

【vue-cli】vue-cli@2源码学习

vue-cli 2 源码 @vue/cli: 3.11.0创建项目 vue create 项目名称 @vue/cli: 2.x.x 创建项目 vue init webpack yhh-project 脚手架初始化项目流程: 下载vue/cli@2 源码 下载完成后初始化 npm i 创建项目 vue init webpack yhh-project vue-init: bin/vue-init #!/usr/bin/e…

大模型数据分析平台 LangSmith 介绍

[​LangSmith​]​ 是 LangChain 自主研发的 LLM 应用程序开发、监控和测试的平台。 LangChain 是一款使用 LLM 构建的首选开源框架&#xff0c;一个链接面向用户程序和 LLM 之间的一个中间层&#xff0c;允许 AI 开发者将像 ​​GPT-4​​ 、文心一言等大型语言模型与外部的计…

springboot依赖之JDBC API手写sql 管理数据库

JDBC API 依赖名称: JDBC API 功能描述: Database Connectivity API that defines how a client may connect and query a database. 数据库连接 API&#xff0c;定义客户端如何连接和查询数据库。 JDBC API 是 Java 标准库的一部分&#xff0c;提供低级别的数据库访问。需要…

基于SpringBoot+Vue的超市进销存系统(带1w+文档)

基于SpringBootVue的超市进销存系统(带1w文档) 基于SpringBootVue的超市进销存系统(带1w文档) 本系统提供给管理员对首页、个人中心、员工管理、客户管理、供应商管理、承运商管理、仓库信息管理、商品类别管理、 商品信息管理、采购信息管理、入库信息管理、出库信息管理、销售…

一次通过PMP考试的学习经验分享

很开心的收到PMI发来的邮件&#xff0c;祝贺我通过了PMP考试。应助教老师的邀请&#xff0c;简单说下我的一些学习备考经验&#xff0c;希望能给即将参加考试的大家带来一些收获。 第一&#xff0c;听基础课&#xff0c;截图做笔记 课程时间对我来说&#xff0c;还是蛮长的。…

MQA(Multi-Query Attention)详解

论文名称&#xff1a;Fast Transformer Decoding: One Write-Head is All You Need 论文地址&#xff1a;https://arxiv.org/abs/1911.02150v1 MQA(Multi-Query Attention)是Google团队在2019年提出的&#xff0c;是MHA (Multi-head Attention&#xff0c;多头注意力机制)的一…

微信运营新助手:自动回复神器,让沟通更高效!

在现代职场中&#xff0c;效率是成功的关键。然而&#xff0c;我们经常会面对大量重复且繁琐的日常任务&#xff0c;消耗宝贵的时间和精力。 今天&#xff0c;我想向大家分享一个强大的微信自动回复神器&#xff0c;它将帮助你高效管理沟通&#xff0c;提升工作效率。 1、自动…

GraphHopper:开源路线规划引擎

在当今信息爆炸的时代&#xff0c;我们越来越依赖于智能路线规划来帮助我们节省时间、提高效率。GraphHopper作为一款开源的路线规划引擎&#xff0c;为我们提供了一个强大而灵活的工具&#xff0c;让我们可以在自己的应用程序中实现高效的路径计算。 什么是GraphHopper&#…

电脑录屏怎么录?2024四大工具助你轻松录制每一刻!

无论是教学演示、游戏直播&#xff0c;还是工作汇报&#xff0c;一款好用的录屏软件都能帮助我们轻松完成任务。那么&#xff0c;电脑录屏怎么录呢&#xff1f;今天为大家推荐几款实用的电脑录屏工具&#xff0c;让你轻松成为录屏达人&#xff01; Foxit REC&#xff1a;专业与…

Linux进程控制——进程程序替换、bash的模拟实现

文章目录 exec系列函数execlexeclp和execle execv系列函数bash的模拟实现实现思路完整代码其他问题 在学习进程的时候&#xff0c;我们想fork一个子进程&#xff0c;然后就可以给他布置任务了 但是如果我们分成两个人开发&#xff0c;父子进程分别负责不同的任务&#xff0c;等…