红黑树介绍

1 问题引入

为什么有AVL树,还要引入红黑树?
在进行多次的插入和删除时:
        1)AVL树会存在大量的旋转操作,追求的是严格平衡;
        2)红黑树通过为节点增加颜色来换取增删节点时旋转次数的降低,任何不平衡都会在三次旋转之内解决,追求的是部分平衡;
因此,在增删节点时,根据不同的情况,AVL树旋转的次数比红黑树多。

2 红黑树的概念

1)是一种二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色(红色/黑色);
2)通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡。即最长路径不会超过最短路径的2倍;比如,假设最短路径是h,最长就是2h,其他路径的长度则为[h,2h]。

2.1 红黑树的性质

1)每个节点不是红色就是黑色;
2)根节点是黑色的;
3)如果一个节点是红色的,则它的俩个孩子节点必须是黑色的。即没有连续的红色节点;但是并没有说黑色节点的孩子必须是红色节点;
4)对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。即每条路径都会包含相同数量的黑色节点;
5)每个叶子结点都是黑色的(此处的叶子结点指的是空节点,即NIL节点),避免数错了路径个数(路径:从根走到NIL节点)。

所以我们可以根据上面的性质思考一下,为什么满足上面的性质,红黑树就能保证:从根结点到NULL节点,其最长路径中节点个数不会超过最短路径节点个数的俩倍?

 

在极端场景下我们可以进行分析:

整体红黑树图,此时满足红黑树的性质:

 

最短路径:全黑

 最长路径:一黑一红

 

2.2 AVL树与红黑树的比较

1)AVL树的高度接近log2N;
2)红黑树的高度,如果是从最短路径看,则是全黑,相当于一个满二叉树;

 

而满二叉树的高度计算如下:
                                 N=2h−1 --》 h=log2(N+1) ≈ log2N;
那么我们可以知道,最长路径(一黑一红),则是2* log2N;
我们可以举一个实际的例子:
假如有10亿个数,那么log2N=10亿 --》N=30;
AVL树:需要寻找30次;
红黑树:需要寻找60次;
虽然红黑树寻找的次数比AVL数多,但是对于现有的cpu来说,并没有太大的影响。

总结:
1)从查找效率来说,AVL树与红黑树相差不大;
2)从增删操作来说,AVL树需要通过多次旋转来降低高度来保持严格平衡,但是旋转是有代价的。而红黑树并不是这样,只是近似平衡,从而减少了旋转的次数。
总的来说,红黑树的效率要比AVL树好

3 红黑树的定义及插入操作讨论

3.1 红黑树的定义

enum Colour
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//新增节点给为红色{}
};

3.2 红黑树的插入操作讨论

红黑树也是一颗二叉搜索树,所以当进行插入操作的时候我们也可以通过之前所学系的AVL树的插入操作进行实行。 但是在插入过程中我们得遵循红黑树的性质,因此,我们对其进行了一些讨论,如下:

首先,我们先定义一个红黑树操作的框架:

template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K,V>& kv){if (_root == nullptr){_root = new Node(kv);//但是要注意的是红黑树的根是黑色的_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新插入结点是红色,这是在初始化的时候就定义的红色if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//检验是否满足红黑树的性质,所以在此进行讨论}
private:Node* _root;
};
3.2.1 实施过程中的问题

根节点是黑色,这是红黑树的性质要求的。那么新插入结点开始为什么要是红色?

因为根据红黑树的性质要求,每条路径的黑色节点的数量都是相同的,那么如果插入结点是黑色的话,那么其他路径就都少了一个黑色节点,这样调整起来工程量较大,十分麻烦。所以插入结点颜色一定是红色。但是如果插入节点的父亲节点是黑色,那是没有问题的。若插入结点的父亲节点是红色的,那么就会违反红黑树的性质(没有连续的红色节点),那就需要进行处理。

3.2.2 对于叔叔节点(u)的讨论

接下来,我们插入红色节点,将父亲节点设定为红色,从而破坏了红黑树(没有连续的红色节点)的性质。 因为爷爷节点(g)、父亲节点(p)和当前节点(cur)都是固定的,所以下面我们将要对叔叔节点(u)进行讨论。
假设 :
①g(grandfather):爷爷节点
②p(parent):父亲节点
③u(uncle):叔叔节点
④cur(current):当前节点

1)情形一 :叔叔节点(u)存在且为红色

g = 黑色,p = 红色,u = 红色 ,cur = 红色。

 问题说明:
①孩子节点(cur)是红色的,父亲节点(p)是红色的,爷爷节点(g)是黑色的,此时叔叔节点是我们的关键。
②根据红黑树的性质,俩个红色不能相连,所以我们需要将父亲节点(p)变为黑色,但是父亲节点(p)变黑,那么叔叔节点(u)就少了一个黑色。
此时我们将爷爷节点(g)变为红色。

 ③但是,此时叔叔节点(u)的路径就少了一个黑色节点。因此,我们将叔叔节点(u)变为黑色。

 ④爷爷节点(g)是否为根节点?
        1)若爷爷节点(g)是根节点,则将爷爷节点(g)变为黑色。

        2)若爷爷节点(g)是子树,爷爷节点(g)一定有父亲节点,且爷爷节点(g)的父亲如果是红色,则需要继续向上调整。 

 以上为第一种情况的抽象图,下面我们对具体的图形进行解析:

①a/b/c/d/e为空树

②c/d/e是下面4个子树中的一种(一个黑色节点):

 从上我们可以看一下共有多少种情况:
c/d/e为上面4种中的任意一种:4×4×4=64种
插入位置共有4中位置
所以此红黑树共有64×4=256种情况。

总结(情况一(叔叔节点(u)存在且为红)的解决方案):

①p/u变为黑色,g变为红色;
②如果g为根,再把g变为黑色;
③若g不为根,则继续往上处理(g可以当做现在的cur);
④p/u是g的左或者右没有影响,cur是p的左或者右也没有影响,处理方式是相同的。

2)情况二:叔叔节点(u)不存在/叔叔节点(u)存在且为黑

①叔叔节点(u)不存在,则当前节点(cur)就是新增,因为如果cur不是新增节点,则cur和p一定有一个节点的颜色是黑色,就不满足每条路径黑色节点个数相同的性质。

 ②叔叔节点(u)存在,则一定是黑色。(因为改为叔叔节点(u)存在,且为红色的情况我们已经分析了),那么当前节点(cur)则必定为黑色。 第二步中的cur节点为红色是因为在调整过程中由黑色变为了红色。

 总结(情况二:叔叔节点(u)不存在/叔叔节点(u)存在且为黑的解决方案):

①p为g的左孩子,cur为p的左孩子,则进行右单旋;
②p为g的右孩子,cur为p的右孩子,则进行左单旋;
③p、g变色:p变为黑色,g变为红色。

3)情况三:叔叔节点(u)不存在/叔叔节点(u)存在且为黑

虽然情况三和情况二的条件是一样的,但是cur和p不在同一侧。

1)叔叔节点(u)不存在,cur为新增节点

 2)叔叔节点(u)存在且为黑

 

总结(情况三:叔叔节点(u)不存在/叔叔节点(u)存在且为黑的解决方案):

1)p为g的左孩子,cur为p的右孩子,则进行左右双旋+变色;
2)p为g的右孩子,cur为p的左孩子,则进行右左双旋+变色;
3)g、cur变色:g变为红色,cur变为黑色。

3.3 插入操作完整代码

bool Insert(const pair<K,V>& kv){if (_root == nullptr){_root = new Node(kv);//但是要注意的是红黑树的根是黑色的_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新插入结点是红色,这是在初始化的时候就定义的红色if (cur->_kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//检验是否满足红黑树的性质,所以在此进行讨论//1.此时cur为红,parent为红,此时就违反了红黑树的性质//8.继续向上处理的判断是parent存在且为红色的情况,所以记性增加parent存在的情况while (parent && parent->_col == RED){//3.插入之前可以认为是红黑树,cur为红,parent为红,则肯定有爷爷节点Node* grandfather = parent->_parent;//4.此时,cur、parent、grandfather是固定的,则需要找uncle节点,从而看如何变化使其不违反红黑树性质if (parent == grandfather->_left){Node* uncle = grandfather->_right;//5.情况一:叔叔节点uncle存在且为红if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//6.继续往上处理cur = grandfather;parent = cur->_parent;//7.第一种情况如果g为根,则将grandfather的颜色变为黑色,这个可以在循环外进行处理//如果parent->_colour == 黑色,则无需进行处理//所以我们需要进行处理的是parent->_colour == 红色,所以我们可以在while循环处进行下一次向上处理判断}else //8.情况二:叔叔节点uncle不存在/为黑色,但是此时不需要考虑uncle节点,因为旋转和变色都与其无关{//9.上面满足了p为g的左孩子,此时应满足cur是p的左孩子条件if (cur == parent->_left){RotalR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//10.情况三:情况三和情况二的条件是一样的,但是cur和p不在同一侧。//此时要求cur是p的右孩子情况else{RotalLR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}//11.如果是双旋,那么因为parent是红色,不能通过while循环判断,并且也不会违反规则了,所以可以直接break;break;}}else{Node* uncle = grandfather->_left;//5.情况一:叔叔节点uncle存在且为红if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//6.继续往上处理cur = grandfather;parent = cur->_parent;//7.第一种情况如果g为根,则将grandfather的颜色变为黑色,这个可以在循环外进行处理//如果parent->_colour == 黑色,则无需进行处理//所以我们需要进行处理的是parent->_colour == 红色,所以我们可以在while循环处进行下一次向上处理判断}else //8.情况二:叔叔节点uncle不存在/为黑色,但是此时不需要考虑uncle节点,因为旋转和变色都与其无关{//9.上面满足了p为g的右孩子,此时应满足cur是p的右孩子条件if (cur == parent->_right){RotalL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//10.情况三:情况三和情况二的条件是一样的,但是cur和p不在同一侧。//此时要求cur是p的右孩子情况else{RotalRL(grandfather);grandfather->_col = RED;cur->_col = BLACK;}//11.如果是双旋,那么因为parent是红色,不能通过while循环判断,并且也不会违反规则了,所以可以直接break;break;}}}//7.如果grandfather是根,则可以再次进行处理_root->_col = BLACK;//2.如果parent不是红色,则说明没有问题return true;}//旋转部分:与AVL树是一样的,只是减少了平衡因子//左单旋void RotalL(Node * parent){//定义阶段Node* subR = parent->_right;Node* subRL = subR->_left;//更改阶段parent->_right = subRL;if (subRL)//子树可能是空{subRL->_parent = parent;}//先记录parent的父结点,因为parent可能是一个子树,也可能是一个根Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;//如果是根if (parent == _root){_root = subR;subR->_parent = nullptr;}else//parent不是根,那么parent可能是父结点的左节点还是右结点{if (parent == ppnode->_left){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}//右单旋void RotalR(Node* parent){//1.定义节点//2.更改阶段//3.注意子树可能为空//4.parent是否为根//5.更新平衡因子Node* subL = parent->_left;Node* subLR = subL->_right;//更改阶段parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (parent == ppnode->_left){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}//左右旋void RotalLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//进行旋转RotalL(parent->_left);//以30为旋转点进行旋转RotalR(parent);//以90为旋转点进行旋转}//右左旋void RotalRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//先进行右旋转,再左旋转RotalR(subR);RotalL(parent);}

4 判断是否为红黑树

如果让你判断一棵树是否为红黑树,你会如何判断?
1)A同学:红黑树中有这样一条概念:最长路径不会超过最短路径的2倍,能否使用这个概念来进行判断?
是不可以的,因为如果这颗树的确满足最长路径不超过最短路径2倍这个概念,但是如果路径的颜色如果不满足红黑树的性质,那么这棵树显而易见也不是红黑树。所以这个思路是不可行的。

2)B同学:我们可以通过红黑树的性质来进行判断这颗树是一颗红黑树?
是可以的,因为若满足红黑树的性质,则可以判断其是一颗红黑树。并且性质中的每条路径的黑色节点数量相同,则可以限制最长路径不会超过最短路径俩倍这个概念。

判断一颗树是否为红黑树,我们主要满足红黑树性质的2、3、4条性质。
其中第2条性质很好判断:

//性质2:根节点是黑色的if (_root->_col != BLACK){return false;}

性质3却存在一个小问题:如果一个节点是红色的,则它的俩个孩子节点必须是黑色的。
那么我们是那当前节点和它的孩子节点比较还是和它的父亲节点进行比较? 如果是孩子节点,那么我们首先需要判断它是否有孩子节点,其次,需判断是它的左孩子还是右孩子,这样写起来,代码就显得比较繁琐复杂。
因此,我们换种思路,如果当前节点和其父亲节点进行比较,那么该节点只有一个父亲节点,并且这样判断也可以满足不能有连续红色节点的性质,所以这样是比较优秀的解法。

//检查是否满足性质3:没有连续的红色节点if (cur->_col == RED && cur->_parent->_col == RED){return false;}

但是性质4确实有些难度:
首先,我们可以使用递归来检验这棵树。 

bool check(Node* cur)
{if(cur == nullptr){return true;}return check(cur->_left) && check(cur->_right);
}
bool IsRBTree()
{if(_root == nullptr){return true;}check(_root);
}

其次,我们可以将上面的我们写好的性质的代码添加进去。

bool check(Node* cur)
{if(cur == nullptr){return true;}//检查是否满足性质3:没有连续的红色节点if (cur->_col == RED && cur->_parent->_col == RED){return false;}return check(cur->_left) && check(cur->_right);
}
bool IsRBTree()
{if(_root == nullptr){return true;}//性质2:根节点是黑色的if (_root->_col != BLACK){return false;}check(_root);
}

最后,我们只需要满足性质4:每条路径有相同数量的黑色节点即可。

 

此时有这样一个思路:

1)首先我们计算一条路径中的黑色节点数量作为一个参考值;
2)递归每条路径并计算黑色节点的数量与这个参考值进行比较,若相等则正确,不相等则报错。

bool check(Node* cur,int BlackNum,int RefNum){if (cur == nullptr){//到达叶子结点时进行判定黑色节点数量与算出来的数量是否相同//检查是否满足性质4if (BlackNum != RefNum){return false;}return true;}//检查是否满足性质3:没有连续的红色节点if (cur->_col == RED && cur->_parent->_col == RED){return false;}//如果当前节点cur为黑色节点,则黑色节点数量++if (cur->_col == BLACK){BlackNum++;}//该节点的左节点和右结点继续递归,没有加&,则表示每一层多少个,不受下一层的影响return check(cur->_left, BlackNum, RefNum) && check(cur->_right,BlackNum,RefNum);}bool IsRBTree(){if (_root == nullptr){return true;}//性质2:根节点是黑色的if (_root->_col != BLACK){return false;}//因为每条路径的黑色节点的个数是相同的//所以我们可以设置一个参考值int RefNum = 0;Node* left = _root;while (left){//如果节点的颜色为黑色,则++if (left->_col == BLACK){RefNum++;}left = left->_left;}//通过check()进行检查return check(_root,0,RefNum);}

我们进行验证这个红黑树是否正确:

int main()
{RBTree<int,int> r1;int a[] = { 3,2,4,5,6,7,8,10,9,1 };for (auto e : a){r1.Insert(make_pair(e,e));}r1.Inorder();cout << r1.IsRBTree() << endl;
}

 验证正确。

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

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

相关文章

Java基础:枚举类enum入门案例

1.基础枚举定义与使用&#xff1a; package com.zxy;public class Main {public static void main(String[] args) { // 获取枚举值cars car cars.BMW;switch (car){case BMW :System.out.println("BMW");break;case BENZ :System.out.println("BENZ&…

torch numpy sort排序出现索引顺序不正常

问题 torch 调用numpy ,numpy 的sort排序值的顺序没发现问题&#xff0c;但是排序的索引argsort()是有明显问题。 方案 多一次取索引 arr.argsort().argsort()参考&#xff1a; https://blog.csdn.net/qq_50571974/article/details/123173118

大模型架构记录5-向量数据库

一 倒排索引、KNN、PQ 1.1 基础版本 query -> requery 对问题做处理&#xff0c;处理上下文 对query 做 refined query 1.2 向量数据库 二 搜索逻辑 2.1 knn 2.2 近似KNN 先和N个空间的均值比较再和空间内部的所有点比较&#xff0c;计算最近值。 优化一&#xff1a; …

Python :数据模型

一. 什么是数据模型&#xff1f; Python数据模型是Python对象系统的抽象&#xff0c;通过一组特殊方法​&#xff08;如__init__、__len__等&#xff09;和协议​&#xff08;如迭代协议、上下文管理协议&#xff09;&#xff0c;定义了对象如何与语言的内置功能&#xff08;如…

考研数学非数竞赛复习之Stolz定理求解数列极限

在非数类大学生数学竞赛中&#xff0c;Stolz定理作为一种强大的工具&#xff0c;经常被用来解决和式数列极限的问题&#xff0c;也被誉为离散版的’洛必达’方法&#xff0c;它提供了一种简洁而有效的方法&#xff0c;使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…

电商平台数据高效集成:旺店通旗舰版到MySQL方案解析

旺店通旗舰版-商品信息查询到MySQL的高效数据集成方案 在现代电商平台中&#xff0c;数据的高效集成和处理是业务成功的关键。本文将分享一个实际案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将旺店通旗舰奇门的数据无缝对接到MySQL数据库&#xff0c;实现商品…

ACL初级总结

ACL–访问控制列表 1.访问控制 在路由器流量流入或者流出的接口上,匹配流量,然后执行相应动作 permit允许 deny拒绝 2.抓取感兴趣流 3.ACL匹配规则 自上而下逐一匹配,若匹配到了则按照对应规则执行动作,而不再向下继续匹配 思科:ACL列表末尾隐含一条拒绝所有的规则 华为:AC…

【微知】plantuml在泳道图中如何将多个泳道框起来分组并且设置颜色?(box “浏览器“ #LightGreen endbox)

泳道分组并且着色 分组用 box和endbox &#xff0c;颜色用#xxx&#xff0c;标注用"xxx" box "浏览器" #LightGreen participant "浏览器1" as Browser participant "浏览器2" as Browser2 endboxparticipant "服务端" as …

C语言:计算并输出三个整数的最大值 并对三个数排序

这是《C语言程序设计》73页的思考题。下面分享自己的思路和代码 思路&#xff1a; 代码&#xff1a; #include <stdio.h> int main() {int a,b,c,max,min,mid ; //设置大中小的数分别为max&#xff0c;mid&#xff0c;min&#xff0c;abc为输入的三个数printf("ple…

【PyMySQL】Python操作MySQL

1、安装pymysql pip install pymysql2、导包 import pymysql3、连接MySQL数据库 db pymysql.connect(hostlocalhost # 本地localhost&#xff0c;或服务器IP地址,userroot # 用户名,passwordpassword # 密码,databasemysql) #数据库名4、创建游标 cursor db.cursor()5、增…

蓝桥备赛(18)- 红黑树和 set 与 map(上)

对于二叉搜索树 &#xff0c; 平衡二叉树 &#xff0c; 以及红黑树 &#xff0c; 目前只需要了解背后的原理 &#xff0c; 不做代码实现的要求 &#xff0c; 重要的就是了解各种操作的时间复杂度即可 &#xff0c; 为set 与 map 做铺垫 一、二叉搜索树 1.1 基本概念 相较与于堆…

【Synchronized】不同的使用场景和案例

【Synchronized】不同的使用场景和案例 【一】锁的作用范围与锁对象【1】实例方法&#xff08;对象锁&#xff09;【2】静态方法&#xff08;类锁&#xff09;【3】代码块&#xff08;显式指定锁对象&#xff09;【4】类锁&#xff08;通过Class对象显式锁定&#xff09; 【二】…

大模型在原发性急性闭角型青光眼预测及治疗方案制定中的应用研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、原发性急性闭角型青光眼概述 2.1 疾病定义与分类 2.2 发病机制与危险因素 2.3 症状与诊断方法 三、大模型在原发性急性闭角型青光眼预测中的应用 3.1 大模型原理与优势 3.2 术前风险预…

【蓝桥杯—单片机】第十五届省赛真题代码题解析 | 思路整理

第十五届省赛真题代码题解析 前言赛题代码思路笔记竞赛板配置建立模板明确基本要求显示功能部分频率界面正常显示高位熄灭 参数界面基础写法&#xff1a;两个界面分开来写优化写法&#xff1a;两个界面合一起写 时间界面回显界面校准校准过程校准错误显示 DAC输出部分按键功能部…

Vue3实战学习(Vue3快速搭建后台管理系统(网页头部、侧边导航栏、主体数据展示区的设计与实现)(超详细))(9)

目录 一、Vue3工程环境配置、项目基础脚手架搭建、Vue3基础语法、Vue3集成Element-Plus的详细教程。(博客链接如下) 二、Vue3集成Element-Plus详细教程。(博客链接如下) 三、Vue3集成Vue-Router详细教程。(博客链接如下) 四、Vue3快速搭建后台管理系统。(实战学习) &#xff08…

halcon机器人视觉(四)calibrate_hand_eye_stationary_3d_sensor

目录 一、准备数据和模型二、按照表面匹配的的结果进行手眼标定三、根据标定结果计算CalObjInCamPose一、准备数据和模型 1、读3D模型:read_object_model_3d 2、创建表面匹配模板:create_surface_model 3、创建一个HALCON校准数据模型:create_calib_data read_object_mode…

【菜鸟飞】通过vsCode用python访问deepseek-r1等模型

目标 通过vsCode用python访问deepseek。 环境准备 没有环境的&#xff0c;vscode环境准备请参考之前的文章&#xff0c;另外需安装ollama&#xff1a; 【菜鸟飞】用vsCode搭建python运行环境-CSDN博客 AI入门1&#xff1a;AI模型管家婆ollama的安装和使用-CSDN博客 选读文章…

vue中,watch里,this为undefined的两种解决办法

提示&#xff1a;vue中&#xff0c;watch里&#xff0c;this为undefined的两种解决办法 文章目录 [TOC](文章目录) 前言一、问题二、方法1——使用function函数代替箭头函数()>{}三、方法2——使用that总结 前言 ‌‌‌‌‌尽量使用方法1——使用function函数代替箭头函数()…

【如何使用云服务器与API搭建专属聊天系统:宝塔面板 + Openwebui 完整教程】

文章目录 不挑电脑、不用技术&#xff0c;云服务器 API 轻松搭建专属聊天系统&#xff0c;对接 200 模型&#xff0c;数据全在自己服务器&#xff0c;安全超高一、前置准备&#xff1a;3 分钟快速上手指南云服务器准备相关账号注册 二、手把手部署教程&#xff08;含代码块&a…

使用 PresentMon 获取屏幕帧率

PresentMon是一个用于捕获和分析Windows上图形应用程序高性能特性的工具集,最初由GameTechDev开发,现由英特尔维护和推广。PresentMon能够追踪关键性能指标,如CPU、GPU和显示器的帧持续时间和延迟等,并支持多种图形API(如DirectX、OpenGL和Vulkan)以及不同的硬件配置和桌…