数据结构~二叉搜索树

news2024/9/28 7:40:35

文章目录

    • 一、二叉搜索树的概念
    • 二、二叉搜索树的结构
      • 二叉搜索树的性能分析
      • 二叉搜索树的插入
      • 二叉搜索树的查找
      • 二叉搜索树的删除
    • 三、二叉搜索树key和key/value使用场景
    • 四、二叉搜索树的练习
      • 将二叉搜索树就地转化为已排序的双向循环链表
      • 从前序与中序遍历序列构造二叉树
      • 二叉树的前序遍历(非递归)
    • 五、完整代码
      • 二叉搜索树的实现代码
      • key/value二叉搜索树代码实现
    • 六、总结

一、二叉搜索树的概念

二叉搜索树又称二叉排序树(Binary Search Tree,BST),它或者是一棵空树,或者是具有以下性质的⼆叉树:

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
  • 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也分别为二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值
    在这里插入图片描述

二、二叉搜索树的结构

二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全⼆叉树),其高度为:O(log2N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为:O(N/2)
所以综合而言⼆叉搜索树增删查改时间复杂度为:O(N)
那么这样的效率显然是无法满足需求的,后续讲解⼆叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。另外需要说明的是,二分查找也可以实现O(logN) 级别的查找效率,但是二分查找有两大缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。这里也就体现出了平衡⼆叉搜索树的价值。
    在这里插入图片描述

二叉搜索树的插入

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给root指针
  2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
  3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要⼀会往右走,⼀会往左走)
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二叉搜索树的查找

  1. 从根开始比较,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
  2. 最多查找高度次,走到到空,还没找到,这个值不存在。
  3. 如果不支持插入相等的值,找到x即可返回
  4. 如果支持插入相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。如下图,查找3,要找到1的右孩子的那个3返回
  5. 在这里插入图片描述

二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩子均为空
  2. 要删除的结点N左孩子位空,右孩子结点不为空
  3. 要删除的结点N右孩子位空,左孩子结点不为空
  4. 要删除的结点N左右孩子结点均不为空

对应以上四种情况的解决方案:

  1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)
  2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
  3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结
  4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N因为这两个结点中任意⼀个,放到N的位置,都满足⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

三、二叉搜索树key和key/value使用场景

key搜索场景:
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进入时扫描车牌在不在系统中,在则抬杆,不在则提示非本小区车辆,无法进入。
场景2:检查⼀篇英文章章单词拼写是否正确,将词库中所有单词放入⼆叉搜索树,读取文章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提示。
在这里插入图片描述
key/value搜索场景:
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和vlaue(中文),搜索时输入英文,则同时查找到了英文对应的中文。
场景2:商场无人值守车库,入口进场时扫车牌,记录车牌和入场时间,出口离场时,扫描车牌,查找入场时间,用当前时间-入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。
场景3:统计⼀篇文章中单词出现的次数,读取⼀个单词,查找单词是否存在,不存在这个说明第⼀次出现,(单词,1),单词存在,则++单词对应的次数。
在这里插入图片描述

四、二叉搜索树的练习

将二叉搜索树就地转化为已排序的双向循环链表

将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。
对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。
在这里插入图片描述

思想:

  • 搜索二叉树走中序是有序的,本题目要求原地修改,也就是不能创建新的结点。
  • 思路1:中序遍历搜索⼆叉树,遍历顺序是有序的,将⼆叉树的结点指针放到⼀个vector中,再把前后结点的链接关系进⾏修改。这个思路最简单,但是需要消耗O(N)的空间复杂度。
  • 思路2:依旧中序遍历搜索二叉树,遍历顺序是有序的,遍历过程中修改左指针为前驱和右指针为后继指针。记录⼀个cur和prev,cur为当前中序遍历到的结点,prev为上⼀个中序遍历的结点,cur->left指向prev,cur->right⽆法指向中序下⼀个,因为不知道中序下⼀个是谁,但是prev->right指向cur;也就是说每个结点的左是在中遍历到当前结点时修改指向前驱的,但是当前结点的右,是在遍历到下⼀个结点时,修改指向后继的。

代码实现

class Solution {
public:
	void InOrderConvert(Node* cur, Node*& prev)
	{
		if (cur == nullptr)
			return;
		// 中序遍历
		InOrderConvert(cur->left, prev);
		// 当前结点的左,指向前⼀个结点
		cur->left = prev;
		// 前⼀个结点的右,指向当前结点
		if (prev)
			prev->right = cur;
		prev = cur;
		InOrderConvert(cur->right, prev);
	}
	Node* treeToDoublyList(Node* root) {
		if (root == nullptr)
			return nullptr;
		Node* prev = nullptr;
		InOrderConvert(root, prev);
		// 从根开始往左⾛,找到第⼀个结点
		Node* head = root;
		while (head->left)
		{
			head = head->left;
		}
		// head为第⼀个结点,prev是最后⼀个结点
		// 题⽬要求为循环链表,进⾏⼀些链接
		head->left = prev;
		prev->right = head;
		return head;
	}
};

从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
在这里插入图片描述
思路:
先序遍历的顺序是根节点、左子树、右子树。这意味着先序遍历的第一个元素就是根节点的值。
中序遍历的顺序是左子树、根节点、右子树。通过在中序遍历中找到根节点的位置,可以确定左子树和右子树的元素范围。
可以根据先序遍历和中序遍历构建出二叉树。这个过程利用了先序遍历确定根节点和中序遍历划分左右子树的特点,通过递归的方式逐步构建出整个二叉

class Solution {
public:
	TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int&
		prei, int inbegin, int inend) {
		if (inbegin > inend)
			return nullptr;
		// 前序确定根
		TreeNode* root = new TreeNode(preorder[prei++]);
		// 根分割中序左右⼦区间
		int rooti = inbegin;
		while (rooti <= inend)
		{
			if (inorder[rooti] == root->val)
				break;
			else
				rooti++;
		}
		// 递归左右⼦区间,递归构建左右⼦树
		// [inbegin, rooti-1] rooti [rooti+1, inend]
		root->left = _buildTree(preorder, inorder, prei, inbegin, rooti - 1);
		root->right = _buildTree(preorder, inorder, prei, rooti + 1, inend);
		return root;
	}
	TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
		int i = 0;
		TreeNode* root = _buildTree(preorder, inorder, i, 0, inorder.size() - 1);
		return root;
	}
};

二叉树的前序遍历(非递归)

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
在这里插入图片描述
在这里插入图片描述
非递归迭代实现思想:
要迭代非递归实现⼆叉树前序遍历,首先还是要借助递归的类似的思想,只是需要把结点存在栈中,方便类似递归回退时取父路径结点。跟这里不同的是,这里把⼀棵⼆叉树分为两个部分:

  1. 先访问左路结点
  2. 再访问左路结点的右子树
    这里访问右子树要以循环从栈依次取出这些结点,循环子问题的思想访问左路结点的右子树。中序和后序跟前序的思路完全⼀致,只是前序先访问根还是后访问根的问题。后序稍微麻烦⼀些,因为后序遍历的顺序是左子树右子树根,当取到左路结点的右子树时,需要想办法标记判断右子树是否访问过了,如果访问过了,就直接访问根,如果访问过需要先访问右子树
class Solution {
public:
	vector<int> preorderTraversal(TreeNode* root) {
		stack<TreeNode*> s;
		vector<int> v;
		TreeNode* cur = root;
		while (cur || !s.empty())
		{
			// 访问⼀颗树的开始
			// 1、访问左路结点,左路结点⼊栈
			while (cur)
			{
				v.push_back(cur->val);
				s.push(cur);
				cur = cur->left;
			}
			// 2、从栈中依次访问左路结点的右⼦树
			TreeNode* top = s.top();
			s.pop();
			// 循环⼦问题⽅式访问左路结点的右⼦树 --
			cur = top->right;
		}
		return v;
	}
};
class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
		stack<TreeNode*> st;
		TreeNode* cur = root;
		vector<int> v;
		while (cur || !st.empty())
		{
			// 访问⼀颗树的开始
			// 1、左路结点⼊栈
			while (cur)
			{
				st.push(cur);
				cur = cur->left;
			}
			// 访问问左路结点 和 左路结点的右⼦树
			TreeNode* top = st.top();
			st.pop();
			v.push_back(top->val);
			// 循环⼦问题⽅式访问右⼦树
			cur = top->right;
		}
		return v;
	}
};
class Solution {
public:
	vector<int> postorderTraversal(TreeNode* root) {
		TreeNode* cur = root;
		stack<TreeNode*> s;
		vector<int> v;
		TreeNode* prev = nullptr;
		while (cur || !s.empty())
		{
			// 1、访问⼀颗树的开始
			while (cur)
			{
				s.push(cur);
				cur = cur->left;
			}
			TreeNode* top = s.top();
			// top结点的右为空 或者 上⼀个访问结点等于他的右孩⼦
			// 那么说明(空)不⽤访问 或者 (不为空)右⼦树已经访问过了
			// 那么说明当前结点左右⼦树都访问过了,可以访问当前结点了
			if (top->right == nullptr || top->right == prev)
			{
				s.pop();
				v.push_back(top->val);
				prev = top;
			}
			else
			{
				// 右⼦树不为空,且没有访问,循环⼦问题⽅式右⼦树
				cur = top->right;
			}
		}
		return v;
	}
};

五、完整代码

二叉搜索树的实现代码

// Binary Search Tree
template<class K>
class BSTree
{
	typedef BSTNode<K> Node;
public:
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 0-1个孩⼦的情况 
				// 删除情况1 2 3均可以直接删除,改变⽗亲对应孩⼦指针指向即可
					if (cur->_left == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur -> _right;
							else
								parent->_right = cur -> _right;
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur -> _left;
							else
								parent->_right = cur -> _left;
						}
						delete cur;
						return true;
					}
					else
					{
						// 2个孩⼦的情况 
						// 删除情况4,替换法删除 
						// 假设这⾥我们取右⼦树的最⼩结点作为替代结点去删除
						// 这⾥尤其要注意右⼦树的根就是最⼩情况的情况的处理,对应课件图中删除8的情况
						// ⼀定要把cur给rightMinP,否会报错。 
						Node* rightMinP = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinP = rightMin;
							rightMin = rightMin->_left;
						}
						cur->_key = rightMin->_key;
						if (rightMinP->_left == rightMin)
							rightMinP->_left = rightMin -> _right;
						else
							rightMinP->_right = rightMin -> _right;
						delete rightMin;
						return true;
					}
			}
		}
		return false;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
	 if (root == nullptr)
	 {
		 return;
	 }
	 _InOrder(root->_left);
	 cout << root->_key << " ";
	 _InOrder(root->_right);
 }
private:
	Node* _root = nullptr;
};

key/value二叉搜索树代码实现

template<class K, class V>
struct BSTNode
{
	// pair<K, V> _kv;
	K _key;
	V _value;
	BSTNode<K, V>* _left;
	BSTNode<K, V>* _right;
	BSTNode(const K& key, const V& value)
		:_key(key)
		, _value(value)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
template<class K, class V>
class BSTree
{
	typedef BSTNode<K, V> Node;
public:
	BSTree() = default;
	BSTree(const BSTree<K, V>& t)
	{
		_root = Copy(t._root);
	}
	BSTree<K, V>& operator=(BSTree<K, V> t)
	{
		swap(_root, t._root);
		return *this;
	}
	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key, value);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
							parent->_left = cur -> _right;
						else
							parent->_right = cur -> _right;
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
							parent->_left = cur -> _left;
						else
							parent->_right = cur -> _left;
					}
					delete cur;
					return true;
				}
				else
				{
					Node* rightMinP = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left)
					{
						rightMinP = rightMin;
						rightMin = rightMin->_left;
					}
					cur->_key = rightMin->_key;
					if (rightMinP->_left == rightMin)
						rightMinP->_left = rightMin -> _right;
					else
						rightMinP->_right = rightMin -> _right;
					delete rightMin;
					return true;
				}
			}
		}
		return false;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newRoot = new Node(root->_key, root->_value);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);
		return newRoot;
	}
private:
	Node* _root = nullptr;
};
void test1()
{
	BSTree<string, string> dict;
	//BSTree<string, string> copy = dict;
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("insert", "插⼊");
	dict.Insert("string", "字符串");
	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << "->" << ret->_value << endl;
		}
		else
		{
			cout << "无此单词,请重新输⼊" << endl;
		}
	}
}
void  test2()
{
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠","苹果", "⾹蕉", "苹果", "⾹蕉" };
	BSTree<string, int> countTree;
	for (const auto& str : arr)
	{
		// 先查找⽔果在不在搜索树中 
		// 1、不在,说明⽔果第⼀次出现,则插⼊<⽔果, 1> 
		// 2、在,则查找到的结点中⽔果对应的次数++ 
		//BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

int main()
{
	test1();
	test2();
	return 0;
}

六、总结

本章节和之后的文章使用的语言从C变为C++
优点与缺点:

  • 优点:
    • 查找、插入、删除操作的时间复杂度在平均情况下为 ,效率较高,特别是对于经常进行动态插入和查找操作的数据集合非常适用。
    • 中序遍历可以方便地得到有序的数据序列,无需额外的排序操作。
  • 缺点:
    • 最坏情况下(例如树退化为链表),查找、插入、删除操作的时间复杂度会退化到 ,性能下降严重。
    • 二叉搜索树的平衡情况依赖于插入和删除操作的顺序,如果数据的插入顺序不合理,可能会导致树的不平衡,影响操作的效率。
  • 应用场景:
    • 二叉搜索树常用于实现动态集合的数据结构,例如在数据库索引、文件系统的目录结构、编译器的符号表等场景中广泛应用,能够快速地进行数据的查找、插入和删除操作。

二叉搜索树在 C++ 中是一种重要的数据结构,掌握其基本操作和遍历方式对于解决各种实际问题具有重要意义。

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

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

相关文章

jmeter-请求参数加密-MD5加密

方法1 &#xff1a;使用jmeter自带的函数助手digest Tool(工具)---Function Helper Dialog(函数助手对话框) 第一个参数是要md5加密的值&#xff0c;第二个参数是保存加密后值的变量 &#xff08; 此处变量是从txt文件导入的&#xff0c;所以使用的是${wd} &#xff09; …

excel统计分析(1):列联表分析与卡方检验

列联表&#xff1a;用于展示两个或多个分类变量之间频数关系的表格。——常用于描述性分析卡方检验&#xff1a;通过实际频数和期望频数&#xff08;零假设为真情况下的频数&#xff09;&#xff0c;反映了观察频数与期望频数之间的差异程度&#xff0c;来评估两个变量是否独立…

Metasploit渗透测试之服务端漏洞利用

简介 在之前的文章中&#xff0c;我们学习了目标的IP地址&#xff0c;端口&#xff0c;服务&#xff0c;操作系统等信息的收集。信息收集过程中最大的收获是服务器或系统的操作系统信息。这些信息对后续的渗透目标机器非常有用&#xff0c;因为我们可以快速查找系统上运行的服…

System Timer (STM)

文章目录 1. 介绍2. 功能特性3. 应用场景4. 功能介绍4.1 TIME0 ~TIME6计数器精度与定时范围4.2 比较器工作原理4.3 中断处理 5. Ifx Demo5.1 STM_Interrupt_1_KIT_TC277_TFT5.2 STM_System_Time_1_KIT_TC275_LK5.3 SMU_Reset_Alarm_1_KIT_TC275_LK 1. 介绍 Ifx TC37x拥有3个自…

前端大模型入门:使用Transformers.js实现纯网页版RAG(一)

我将使用两篇文章的篇幅&#xff0c;教大家如何实现一个在网页中运行的RAG系统。本文将其前一半功能&#xff1a;深度搜索。 通过这篇文章&#xff0c;你可以了解如何在网页中利用模型实现文本相似度计算、问答匹配功能&#xff0c;所有的推理都在浏览器端本地执行&#xff0c;…

C语言-IO

一,阻塞IO与非阻塞IO 简介: IO的本质是基于操作系统接口来控制底层的硬件之间数据传输&#xff0c;并且在操作系统中实现了多种不同的 IO 方式&#xff08;模型&#xff09;&#xff0c;比较常见的有下列三种 阻塞型IO模型 非阻塞型IO模型 多路复用IO模型 在 C 语言中&#…

牛客SQL练习详解 02:条件查询

牛客SQL练习详解 02&#xff1a;条件查询 1、基础排序sql36 查找后排序sql37 查找后多列排序sql38 查找后降序排列 2、基础操作符sql6 查找学校时北大的学生信息sql7 查找年龄大于24岁的用户信息sql8 查找某个年龄段的用户信息sql9 查找chuchu 3、高级操作符sql11 高级操作符练…

认知杂谈91《菜鸟的自我修炼:减少过度干预》

内容摘要&#xff1a;          在投资和生活中&#xff0c;动作过多往往因情绪波动和缺乏计划而引发亏损。历史上的安史之乱和现代投资中的频繁交易都是例证。要管理情绪&#xff0c;首先要认识自己的情绪模式&#xff0c;然后改变消极的思考方式&#xff0c;并通过合…

『USB3.0Cypress』QT基于cyusb_linux_1.0.5开发上位机

文章目录 1.CyUSB Suite2.搭建开发环境3.Cyusb的应用4.疑问解决5.传送门1.CyUSB Suite CyUSB Suite for Linux是一个围绕现有开源用户空间USB库libusb的wrapper。CyUSB套件通过围绕libusb的简化包装器以及在下载固件后提供用于测试外围设备的基础设施,让您快速入门。换句话说…

1.6 物理层

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 物理层的基本概念1.1 定义1.2 作用1.3 物理层的主要任务 2 数据通信的基础知识2.1 常用术语2.2 信号2.3 码元2.4 信道2.5 数据通信系统模型 3 信道的极限容量3.1 基本术…

LabVIEW提高开发效率技巧----合理管理程序架构

在LabVIEW开发中&#xff0c;合理管理程序架构是保持项目可维护性和扩展性的关键。随着项目复杂度的增加&#xff0c;良好的架构设计可以避免代码混乱&#xff0c;并且便于后期的修改和扩展。以下是两种常见且有效的架构管理方式&#xff1a; 1. 面向对象编程&#xff08;OOP&a…

Sony IMX334LQR-C 1/1.8寸 8.42 M像素

索尼IMX334LQR宽动态超星光级交通监测CMOS 封装&#xff1a;LGA 对⾓线&#xff1a;8.86 mm&#xff08;类型1/1.8&#xff09; 索尼IMX334LQR宽动态超星光级交通监测CMOS的参数及规格书资料&#xff1a; IMX334LQR-C是⼀个对⾓线8.86 mm&#xff08;类型1/1.8&#xff09;的…

安科瑞Acrel-1000DP分布式光伏监控系统在鄂尔多斯市鄂托克旗巴音乌苏六保煤矿5MW分布式光伏项目中的应用

安科瑞 华楠 摘 要&#xff1a;分布式光伏发电就是将太阳能光伏板分散布置在各个区域&#xff0c;通过小规模、模块化的方式实现电能的并网或独立使用&#xff0c;这种发电方式具有就近发电、就近并网、就近转换、就近使用的特点。近年来&#xff0c;技术进步和政策支持推动了光…

8086介绍

内部结构 执行部件EU&#xff08;Execution Unit&#xff09; 包含运算器、通用寄存器组、EU控制单元。 只负责控制&#xff0c;不和外部总线打交道 总线接口部件BIU&#xff08;Bus Interface Unit&#xff09; 包含指令队列缓冲器、16位指令指针寄存器IP、16位段寄存器&am…

TypeScript入门 (五)异步编程与前后端交互

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的TypeScript学习总结文档。本文旨在全面介绍 TypeScript 中的异步编程与网络请求&#xff0c;帮助读者深入理解 TypeScript 中的 asyn…

Colorful/七彩虹将星X17 XS 22 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

Redis 五大基本数据类型及其应用场景进阶(缓存预热、雪崩 、穿透 、击穿)

Redis 数据类型及其应用场景 Redis 是什么? Redis是一个使用C语言编写的高性能的基于内存的非关系型数据库&#xff0c;基于Key/Value结构存储数据&#xff0c;通常用来 缓解高并发场景下对某一资源的频繁请求 &#xff0c;减轻数据库的压力。它支持多种数据类型,如字符串、…

Linux安装JDK及配置环境变量超详细教程

微服务Linux解析部署使用全流程 linux系统的常用命令 Linux安装vim超详细教程 Linux安装tomcat及配置环境变量超详细教程 1、上传压缩包 统一创建目录&#xff1a;/usr/local/jdk&#xff0c;将压缩包上传到这个目录下。拖动文件到这个目录下即可。 2、执行解压命令 先进…

ELMO理论

目录 1 优点 2 缺点 3.知识点个人笔记 2018年3月份&#xff0c;ELMo出世&#xff0c;该paper是NAACL18 Best Paper。在之前2013年的word2vec及2014年的GloVe的工作中&#xff0c;每个词对应一个vector&#xff0c;对于多义词无能为力。ELMo的工作对于此&#xff0c;提出了一…

在 Gitlab 中使用 ChatGPT 进行 CodeReview

ChatGPT集成Gitlab&#xff0c;实现自动代码审计并进行评论&#xff0c;为软件开发团队提供高效、智能的代码审查解决方案。支持其他模型如通义千问等 自动触发与及时响应&#xff1a;利用Gitlab的Webhook功能&#xff0c;实现代码提交、合并请求和标签创建等事件的自动触发。一…