【STL】平衡二叉树

前言

对于之前普通的二叉搜索树,其搜索的效率依靠树的形状来决定,如下:

d6b39cc3ecbe4318a5bbd780ecefb9b3.png

可以看到 A图 中的树比较彭亨,搜索一个元素的效率接近 O(logN) ;而 B图 中的形状也符合搜索二叉树,但是很不平衡,这时的搜索效率就变成 O(N) 了(如搜索值为4的节点)

可以看到,如果搜索二叉树不平衡,他的搜索的效率不确定,在 O(logN) 和 O(N) 之间,可能有暴击;

那么对此,既然不平衡,那我们就给出平衡的两种方法:

1.构成AVL树

2.构成红黑树

这两种方法都是为了平衡搜索二叉树而生

AVL树

1. AVL树的概念和性质

它的出现是为了 搜索二叉树的平衡,那就来了解AVL树的概念和性质,也就是什么是AVL,并且作为一颗AVL树的要求是什么

首先AVL树是一颗树,它要求任何一个节点的左右子树的高度差不超过一,以此来保证平衡,是AVL树的核心

来看看它的做法

与搜索二叉树不同,它将二叉链,变成了三叉链(多了指向父节点的指针),并且引入了平衡因子这个东西;这些都是为了方便并且为了树的一个平衡引入

三叉链和平衡因子和下面AVL树属性的图结合起来看更直观

三叉链也就是多了一个 parent指针 指向该节点的父节点,这一操作可以让节点找到其祖先

平衡因子是指该节点的 右子树高度 减去 左子树高度的值,而根据AVL树的性质(要求任何一个节点的左右子树的高度差不超过一),也就是平衡因子 _bf 的值域为 { -1, 0, 1 } ,如果平衡因子 _bf 的值出现为 2 或者 -2 ,说明该树已经不平衡,此时需要进行停止调整,先进行旋转操作

2. AVL树类的属性

这里的节点值类型简化为了int类型,原为 pair

8d3c11a09a094a64b8137292a659eedc.png

bd509a65fd8946ffbb38ca7fb689e048.png

3. AVL树的插入函数

而AVL树的重点呢,也就是AVL树是如何让树达到平衡的呢?就是每插入了一个节点后,进行了一些调整,如果插入节点后不平衡,那么会将其调整成平衡

插入后对插入节点祖先的平衡因子进行调整,当调整过程中,有平衡因子出现异常时,此时通过旋转的方式,让树继续保持平衡

当平衡因子出现异常即是 平衡因子的值更新为 2 或者 -2 时,说明树现在已经不平衡,需要进行旋转

分情况讨论:

a6f7bbde65044588ad1a8e0c6d46229b.png

现在平衡因子已经出现了不平衡,为什么旋转之后,树就可以继续平衡呢?

具体看到旋转 (这里以左单旋为例):

定义节点指针 parent(图中的节点值为30), cur(图中的60)

左单旋是将 cur 的左孩子给给 parent 的右, 再把 parent 赋值给 cur 的左,旋转完成

再进行平衡因子的更新:parent 和 cur 的平衡因子 _bf 都变成 0

32e08c9f357a4f2db020b7bdfb4e58a1.png

左单旋具体代码如下:

        void RotateL(Node* parent){assert(parent);Node* cur = parent->_right;Node* cur_left = cur->_left;// 改变节点之间的关系parent->_right = cur_left;cur->_left = parent;// 改变_parent的指向//原本 parent 的 _parentNode* ppNode = parent->_parent;//若原本 ppNode(原本 parent 的 _parent)为空,说明parent原本不是根,现需把             ppNode 给到 cur 的 _parent,并且把 ppNode 的子更新为 curif (ppNode){if (ppNode->_left == parent){ppNode->_left = cur;}else{ppNode->_right = cur;}cur->_parent = ppNode;}//若原本 parent->_parent 为空,说明parent原本是根,现需把_root更新为根else{_root = cur;cur->_parent = nullptr;}if (cur_left){cur_left->_parent = parent;}parent->_parent = cur;// 更新平衡因子cur->_bf = parent->_bf = 0;}

对左单旋的总结是:当不平衡的树进行左单旋之后,树又被调整为平衡,然后再进行平衡因子的更新

那么右单旋的思路是一样的,自己可以进行推理

而要进行双旋时候,可以再进行讨论(这里以右左旋进行讨论):

8cd424f049604210a6c94074d65f57a3.png

右左旋:定义 parent(下图节点值为30), cur(下图节点值为90), cur_left(下图节点值为60)

双旋都是两大步,这里右左旋就是先进行右旋,再进行左旋,旋转结束;对,这里对刚刚实现的单旋代码进行了复用,复用yyds

具体是对 cur 先进行右旋,再对 parent 进行左旋,如下

dcb5820617d4473c95c273a54add59f1.png

最后进行平衡因子的调整,但是这里特别容易忘记第三种情况的讨论,AVL树的这个细节得注意:

188fbf8bb0974858b161474841cdfb82.png

右左旋的代码如下:

        void RotateRL(Node* parent){Node* cur = parent->_right;Node* cur_left = cur->_left;int bf = cur_left->_bf;RotateR(parent->_right);RotateL(parent);parent->_bf = 0;cur->_bf = 0;cur_left->_bf = 0;if (bf == -1){cur->_bf = 1;}if (bf == 1){parent->_bf = -1;}}

那么左右旋的思路是一样的,自己可以进行推理

4. 总结

而进行何种旋转本质是取决于 parent cur 和 cur_left (或cur_right)之间的关系,若他们三个所连成的是直线,那么进行单旋操作,如果为折线,那就进行双旋操作;而这里的平衡因子刚好可以体现他们之间的关系,所以上图中根据平衡因子来决定单双旋和左右旋转

AVL树构造和插入函数,测试函数:

这里的节点值类型简化为了int类型,原为 pair

#pragma once#include <iostream>using namespace std;#include <string>#include <array>#include <deque>#include <list>#include <map>#include <queue>#include <set>#include <stack>#include <unordered_map>#include <unordered_set>#include <vector>#include <algorithm>#include <assert.h>#include <windows.h>#include <ctime>namespace zhuandrong
{class AVLTreeNode{typedef AVLTreeNode Node;public:int _val;Node* _left;Node* _right;Node* _parent;int _bf;public:AVLTreeNode(const int val = 0): _val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}};class AVLTree{typedef AVLTreeNode Node;protected:void RotateL(Node* parent){assert(parent);Node* cur = parent->_right;Node* cur_left = cur->_left;// 改变节点之间的关系parent->_right = cur_left;cur->_left = parent;// 改变_parent的指向//原本 parent 的 _parentNode* ppNode = parent->_parent;//若原本 ppNode(原本 parent 的 _parent)为空,说明parent原本不是根,现需把 ppNode 给到 cur 的 _parent,并且把 ppNode 的子更新为 curif (ppNode){if (ppNode->_left == parent){ppNode->_left = cur;}else{ppNode->_right = cur;}cur->_parent = ppNode;}//若原本 parent->_parent 为空,说明parent原本是根,现需把_root更新为根else{_root = cur;cur->_parent = nullptr;}if (cur_left){cur_left->_parent = parent;}parent->_parent = cur;// 更新平衡因子cur->_bf = parent->_bf = 0;}//void RotateL(Node* parent)//{//	Node* cur = parent->_right;//	Node* cur_left = cur->_left;//	parent->_right = cur_left;//	cur->_left = parent;//	Node* ppNode = parent->_parent;//	if (cur_left)//右孩子可能存在,也可能不存在,所以需要判断,需要在parent改变前判断//	{//		cur_left->_parent = parent;//	}//	parent->_parent = cur;//	if (parent == _root)//parent可能是根节点,也可能不是根节点//	{//		_root = cur;//		cur->_parent = nullptr;//	}//	else//	{//		if (ppNode->_left == parent)//		{//			ppNode->_left = cur;//		}//		else//		{//			ppNode->_right = cur;//		}//		cur->_parent = ppNode;//	}//	cur->_bf = parent->_bf = 0;//将平衡因子调整//}void RotateR(Node* parent){assert(parent);Node* cur = parent->_left;Node* cur_right = cur->_right;// 改变节点之间的关系parent->_left = cur_right;cur->_right = parent;// 改变_parent的指向//原本 parent 的 _parentNode* ppNode = parent->_parent;//若原本 ppNode(原本 parent 的 _parent)为空,说明parent原本不是根,现需把 ppNode 给到 cur 的 _parent,并且把 ppNode 的子更新为 curif (ppNode){if (ppNode->_left == parent){ppNode->_left = cur;}else{ppNode->_right = cur;}cur->_parent = ppNode;}//若原本 parent->_parent 为空,说明parent原本是根,现需把_root更新为根else{_root = cur;cur->_parent = nullptr;}if (cur_right){cur_right->_parent = parent;}parent->_parent = cur;// 更新平衡因子cur->_bf = parent->_bf = 0;}//void RotateR(Node* parent)//{//	Node* cur = parent->_left;//	Node* curRight = cur->_right;//	parent->_left = curRight;//	cur->_right = parent;//	Node* ppNode = parent->_parent;//	if (curRight)//右孩子可能存在,也可能不存在,所以需要判断,需要在parent改变前判断//	{//		curRight->_parent = parent;//	}//	parent->_parent = cur;//	if (parent == _root)//parent可能是根节点,也可能不是根节点//	{//		_root = cur;//		cur->_parent = nullptr;//	}//	else//	{//		if (ppNode->_left == parent)//		{//			ppNode->_left = cur;//		}//		else//		{//			ppNode->_right = cur;//		}//		cur->_parent = ppNode;//	}//	cur->_bf = parent->_bf = 0;//将平衡因子调整//}void RotateRL(Node* parent){Node* cur = parent->_right;Node* cur_left = cur->_left;int bf = cur_left->_bf;RotateR(parent->_right);RotateL(parent);/*parent->_bf = cur_left->_bf = 0;cur->_bf = flag;*/parent->_bf = 0;cur->_bf = 0;cur_left->_bf = 0;if (bf == -1){cur->_bf = 1;}if (bf == 1){parent->_bf = -1;}}void RotateLR(Node* parent){Node* cur = parent->_left;Node* cur_right = cur->_right;int bf = cur_right->_bf;RotateL(parent->_left);RotateR(parent);/*parent->_bf = parent->_parent->_bf = 0;parent->_parent->_left->_bf = -1;*/cur->_bf = 0;parent->_bf = 0;cur_right->_bf = 0;if (bf == 1){cur->_bf = -1;}if (bf == -1){parent->_bf = 1;}}protected:Node* _root;public:AVLTree(): _root(nullptr){}AVLTree(vector<int> v){for (auto& e : v){if (e == 8){int i = 0;}insert(e);assert(IsBalance());}}bool insert(int val){// 判断是否有根节点if (_root){//先找到合适的插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (val < cur->_val){parent = cur;cur = cur->_left;}else if (val > cur->_val){parent = cur;cur = cur->_right;}else{cout << "插入的元素值已重复" << endl;return false;}}cur = new Node(val);cur->_parent = parent;if (val < parent->_val){parent->_left = cur;}else{parent->_right = cur;}//对平衡因子进行更新while (parent){if (cur == parent->_left){--parent->_bf;}else if (cur == parent->_right){++parent->_bf;}//若 parent->_bf == 0,表明平衡因子调整结束if (!parent->_bf){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}//若parent->_bf == 2 && cur->_bf == 1,将parent进行左单旋else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);break;}//若parent->_bf == 2 && cur->_bf == -1,将parent进行右左单旋else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);break;}//若parent->_bf == -2 && cur->_bf == -1,将parent进行右单旋else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);break;}//若parent->_bf == -2 && cur->_bf == 1,将parent进行左右单旋else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);break;}else{assert(false);}}}else{_root = new Node(val);}return true;}int TreeHight(Node* root){if (root == nullptr)return 0;int leftHight = TreeHight(root->_left);int rightHight = TreeHight(root->_right);return leftHight > rightHight ? leftHight + 1 : rightHight + 1;}void Inorder(){_Inorder(_root);}bool IsBalance(){return _IsBalance(_root);}private:void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_val << endl;_Inorder(root->_right);}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftHight = TreeHight(root->_left);int rightHight = TreeHight(root->_right);//检查平衡因子对不对if (rightHight - leftHight != root->_bf){cout << "平衡因子出现异常" << endl;cout << rightHight - leftHight << endl;return false;}//需要递归检查是否平衡return (leftHight - rightHight <= 1 && leftHight - rightHight >= -1)&& _IsBalance(root->_left) && _IsBalance(root->_right);}};void test_AVLTree(){/*vector<int> v = { 6, 4, 32, 2, 7, 8 };AVLTree avl_tree(v);assert(avl_tree.IsBalance());*/AVLTree avl_tree;srand((unsigned int)time(NULL));for (int i = 1000; i >= 0; --i){avl_tree.insert(rand());//avl_tree.insert(i);assert(avl_tree.IsBalance());}//srand((unsigned int)time(0));//const size_t N = 10000;//for (size_t i = 0; i < N; ++i)//{//	size_t x = rand();//	avl_tree.insert(x);//	//cout << t.IsBalance() << endl;//}//avl_tree.Inorder();//cout << avl_tree.IsBalance() << endl;}}

红黑树

1. 红黑树的概念和性质(什么是红黑树,并且作为一颗红黑树的要求)

8f14b4f1c0434c04abce9efca47479a1.png

以上是红黑树的概念和性质,在红黑树里不仅仅要考虑 cur 、curleft 、 parent 还要引入 grandfather,具体为什么看到后面就知道了

2. 红黑树类的属性

是使用枚举枚举出 红 黑 两种情况

58533f47adb74e74bda140814c9b16bd.png

3. 红黑树的插入函数

还是分情况讨论:

4. 总结

根据性质可以分为以下情况:

可以看出第一种情况(uncle存在且为黑)只需要变色即可,在看是否继续向上调整

第二种情况就要旋转了,而如何旋转本质图中也详细说了,取决于 grandfather、parent、cur 之间的关系,其实AVL树的旋转的决定因素也在此,只是转换成平衡因子进行描述

关于红黑,具体的思想关键体现在插入函数中,复习请自行分析出两种大情况,和情况二的细分,然后写出插入函数,才算过关

总体代码:

#pragma once#include <iostream>using namespace std;#include <string>#include <array>#include <deque>#include <list>#include <map>#include <queue>#include <set>#include <stack>#include <unordered_map>#include <unordered_set>#include <vector>#include <algorithm>#include <assert.h>#include <windows.h>#include <ctime>namespace zhuandrong
{enum Color{RED,BLACK};template<typename K, typename T>class RBTreeNode{typedef RBTreeNode Node;public:pair<K, T> _t;Node* _left;Node* _right;Node* _parent;Color color;public:RBTreeNode(pair<K, T> t): _t(t), _left(nullptr), _right(nullptr), _parent(nullptr), color(RED){}};template<typename K, typename T>class RBTree{typedef RBTreeNode<K, T> Node;protected:Node* _root;protected:void RotateL(Node* parent){assert(parent);Node* cur = parent->_right;Node* cur_left = cur->_left;// 改变节点之间的关系parent->_right = cur_left;cur->_left = parent;// 改变_parent的指向//原本 parent 的 _parentNode* ppNode = parent->_parent;//若原本 ppNode(原本 parent 的 _parent)为空,说明parent原本不是根,现需把 ppNode 给到 cur 的 _parent,并且把 ppNode 的子更新为 curif (ppNode){if (ppNode->_left == parent){ppNode->_left = cur;}else{ppNode->_right = cur;}cur->_parent = ppNode;}//若原本 parent->_parent 为空,说明parent原本是根,现需把_root更新为根else{_root = cur;cur->_parent = nullptr;}if (cur_left){cur_left->_parent = parent;}parent->_parent = cur;}void RotateR(Node* parent){assert(parent);Node* cur = parent->_left;Node* cur_right = cur->_right;// 改变节点之间的关系parent->_left = cur_right;cur->_right = parent;// 改变_parent的指向//原本 parent 的 _parentNode* ppNode = parent->_parent;//若原本 ppNode(原本 parent 的 _parent)为空,说明parent原本不是根,现需把 ppNode 给到 cur 的 _parent,并且把 ppNode 的子更新为 curif (ppNode){if (ppNode->_left == parent){ppNode->_left = cur;}else{ppNode->_right = cur;}cur->_parent = ppNode;}//若原本 parent->_parent 为空,说明parent原本是根,现需把_root更新为根else{_root = cur;cur->_parent = nullptr;}if (cur_right){cur_right->_parent = parent;}parent->_parent = cur;}void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}public:RBTree(): _root(nullptr){}RBTree(vector <pair<K, T>> v){for (auto& e : v){if (e.second == 5){int i = 0;}insert(e);}}bool insert(const pair<K, T> t){// 先判断根节点是否为空if (_root){// 若不为空,先找到合适的插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (t.second < cur->_t.second){parent = cur;cur = cur->_left;}else if (t.second > cur->_t.second){parent = cur;cur = cur->_right;}else{//cout << "该元素已经插入,请重试" << endl;return false;}}//找到该位置后进行插入并且链接关系cur = new Node(t);cur->_parent = parent;if (t.second < parent->_t.second){parent->_left = cur;}else{parent->_right = cur;}// 进行调整while (parent && parent->color == RED){Node* grandfather = parent->_parent;Node* uncle = nullptr;if (parent == grandfather->_left){uncle = grandfather->_right;}else{uncle = grandfather->_left;}// 第一种情况:如果uncle存在且为红if (uncle && uncle->color == RED){//将p和u变黑,g变红parent->color = uncle->color = BLACK;grandfather->color = RED;//更新,并且判断更新后的parent是否为根,不为根就继续调整;为根表明调整结束cur = grandfather;parent = cur->_parent;if (parent == _root){break;}}// 第二种情况:如果uncle存在且为黑色或者uncle不存在else{// 当 cur 为 parent 的左孩子时if (parent->_left == cur){if (grandfather->_left == parent){//       g//    p     u// c//Node* uncle = grandfather->_right;RotateR(grandfather);grandfather->color = cur->color = RED;parent->color = BLACK;}else{//      g             g                c//   u     p   --> u     c     -->  g     p//        c                 p      u//Node* uncle = grandfather->_left;RotateRL(grandfather);grandfather->color = parent->color = RED;cur->color = BLACK;}}// 当 cur 为 parent 的右孩子时else{if (grandfather->_right == parent){//    g     // u     p     //        c//Node* uncle = grandfather->_right;RotateL(grandfather);grandfather->color = cur->color = RED;parent->color = BLACK;}else{//      g              g                c//   p     u   -->  c     u     -->  p     g//    c            p                         u//Node* uncle = grandfather->_left;RotateLR(grandfather);grandfather->color = parent->color = RED;cur->color = BLACK;}}break;}}}// 若为空,则该元素作为根节点else{_root = new Node(t);}_root->color = BLACK;return true;}bool judge_color(Node* root, int blacksum, int blacknum){if (!root){if (blacknum != blacksum){return false;}return true;}if (root->color == BLACK){++blacknum;}if (root->color == RED && root->_parent && root->_parent->color == RED){return false;}return judge_color(root->_left, blacksum, blacknum)&& judge_color(root->_right, blacksum, blacknum);}bool isBalance(){if (!_root){cout << "根为空" << endl;return false;}if (_root->color == RED){cout << "根的颜色为红色,非法" << endl;return false;}Node* cur = _root;int blacksum = 0;while (cur){if (cur->color == BLACK){++blacksum;}cur = cur->_right;}return judge_color(_root, blacksum, 0);}};void test_RBTree(){/*vector<pair<int, int>> v;v.push_back(make_pair(1, 13));v.push_back(make_pair(2, 8));v.push_back(make_pair(3, 17));v.push_back(make_pair(4, 1));v.push_back(make_pair(5, 11));v.push_back(make_pair(6, 15));v.push_back(make_pair(7, 25));v.push_back(make_pair(8, 6));v.push_back(make_pair(9, 22));v.push_back(make_pair(10, 27));v.push_back(make_pair(11, 5));RBTree<int, int> rb_tree(v);cout << rb_tree.isBalance() << endl;*/RBTree<int, int> rb_tree;srand((unsigned int)time(NULL));for (int i = 0; i < 1000; ++i){rb_tree.insert(make_pair(i, rand()));}if (rb_tree.isBalance()){cout << "本次测试成功" << endl;}else{cout << "本次测试失败" << endl;}}}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP/IP网络分层模型

TCP/IP当初的设计者真的是非常聪明&#xff0c;创造性地提出了“分层”的概念&#xff0c;把复杂的网络通信划分出多个层次&#xff0c;再给每一个层次分配不同的职责&#xff0c;层次内只专心做自己的事情就好&#xff0c;用“分而治之”的思想把一个“大麻烦”拆分成了数个“…

解决 MyBatis 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题

文章目录 问题简述场景描述问题描述问题原因解决办法 问题简述 笔者在使用 MyBatis 进行一对多查询的时候遇到一个奇怪的问题。对于笔者的一对多的查询结果&#xff0c;出现了这样的一个现象&#xff1a;原来每个组里有多个元素&#xff0c;查询目标是查询所查的组&#xff0c;…

Stable Diffusion绘图,lora选择

best quality, ultra high res, (photorealistic:1.4), 1girl, off-shoulder white shirt, black tight skirt, black choker, (faded ash gray hair:1), looking at viewer, closeup <lora:koreandolllikeness_v20:0.66> 最佳品质&#xff0c;超高分辨率&#xff0c;&am…

Location的匹配

nginx的正则表达式: ^:字符串的起始位置 $:字符窜的结束位置 *:匹配所有 :匹配前面的字符一次或者多次 ?:匹配前面的字符0次或者1次 .:任意单个字符 {n}:连续重复出现n次。 {n,m}:连续重复出现n-m次 [a-Z0-9A-Z] [C]:匹配单个字符c ():分组 |:或 一 Location的分类&#xff1a…

Prompt 驱动架构设计:探索复杂 AIGC 应用的设计之道?

你是否曾经想过&#xff0c;当你在 Intellij IDEA 中输入一个段代码时&#xff0c;GitHub 是如何给你返回相关的结果的&#xff1f;其实&#xff0c;这背后的秘密就是围绕 Prompt 生成而构建的架构设计。 Prompt 是一个输入的文本段落或短语&#xff0c;用于引导 AI 生成模型执…

dockerfile 搭建lnmp+wordpress,docker-compose搭建lnmp+wordpress

目录 dockerfile 搭建lnmpwordpress 部署nginx&#xff08;容器IP 为 172.18.0.10&#xff09; 部署mysql&#xff08;容器IP 为 172.18.0.20&#xff09; 部署php&#xff08;容器IP 为 172.18.0.30&#xff09; docker-compose搭建lnmpwordpress dockerfile 搭建lnmpword…

Linux:mongodb数据逻辑备份与恢复(3.4.5版本)

我在数据库aaa的里创建了一个名为tarro的集合&#xff0c;其中有三条数据 备份语法 mongodump –h server_ip –d database_name –o dbdirectory 恢复语法 mongorestore -d database_name --dirdbdirectory 备份 现在我要将aaa.tarro进行备份 mongodump --host 192.168.254…

基于R语言实现中介效应检验以及sobel检验代码

数据格式 随机数据&#xff0c;不一定好 y是因变量&#xff0c;x是自变量&#xff0c;m是中介变量 基本原理 M ~ X Y ~ X Y ~ X M 直接上代码 library(mediation) library(bda)# 加载readxl包 library(readxl) # 读取Excel表格# 读取数据 我是从剪切板读取的 data read…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

ZIP文件怎么打开?值得收藏的3个方法!

“快帮帮我&#xff01;刚接收到了一个zip文件&#xff0c;但是我不知道应该怎么打开&#xff0c;有没有知道应该如何操作的朋友呀&#xff1f;快来帮帮我吧&#xff01;非常感谢&#xff01;” 在Win10电脑中&#xff0c;打开ZIP文件是一项常见任务&#xff0c;因为ZIP文件是一…

【Mysql】Innodb数据结构(四)

概述 MySQL 服务器上负责对表中数据的读取和写入工作的部分是存储引擎 &#xff0c;而服务器又支持不同类型的存储引擎&#xff0c;比如 InnoDB 、MyISAM 、Memory 等&#xff0c;不同的存储引擎一般是由不同的人为实现不同的特性而开发的&#xff0c;真实数据在不同存储引擎中…

分布式链路追踪如何跨线程

背景 我们希望实现全链路信息&#xff0c;但是代码中一般都会异步的线程处理。 解决思路 我们可以对以前的 Runable 和 Callable 进行增强。 可以使用 ali 已经存在的实现方式。 TransmittableThreadLocal (TTL) 解决异步执行时上下文传递的问题 核心的实现思路如下&#…