当前位置: 首页 > news >正文

[C++随笔录] 红黑树

红黑树

  • 红黑树的特点
  • 红黑树的模拟实现
    • 红黑树的底层结构
    • insert的实现
      • 实现思路
      • 更新黑红比例的逻辑
      • insert的完整代码
    • insert的验证
  • 源码

红黑树的特点

  • 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是 Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的.

  • 红黑树的特点:

    1. 节点颜色不是红色就是黑色
    2. 根节点是黑色的
    3. 每一条路径的黑色节点数目是相同的, (注意: 这里的路径是从根节点到NIL(黑色)节点)
    4. 每一条路径不允许出现连续的红色节点
  • 路径是从根节点 到 NIL节点的

🗨️满足上面的条件, 为啥就能保证 红黑树确保没有一条路径会比其他路径长出俩倍呢?

  • 根据上述的特点, 我们可以得知:
    每条路径的黑色节点数目一定的情况下 , 最短路径是 全黑, 最长路径是 黑红相间的
    如果我们保证 最长路径 不超过 最短路径的二倍就可以了

红黑树的模拟实现

红黑树的底层结构

  1. 颜色类型
// 枚举
enum Color
{RED,BLACK
};
  1. RBTreeNode类
template<class K, class V>
struct RBTreeNode
{
public:RBTreeNode(const pair<K, V> kv):_kv(kv){}public:pair<K, V> _kv;Color _color = BLACK;RBTreeNode<K, V>* _left = nullptr;RBTreeNode<K, V>* _right = nullptr;RBTreeNode<K, V>* _parent = nullptr;
};
  1. RBTree类
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;public:RBTree(){}private:// 根节点Node*  _root = nullptr;// 记录旋转次数int RotateCount =  0;
}

insert的实现

实现思路

二叉搜索树的插入逻辑 + 更新黑红比例

bool Insert(const pair<K, V> kv)
{if (_root == nullptr){// 根节点是黑色的_root = new Node(kv);_root->_color = BLACK;return true;}Node* parent = _root;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 新建一个节点, 默认是红色cur = new Node(kv);cur->_color = RED;// 链接cur 和 parentif (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更改黑红比例// ...// ...// 更新完黑红比例后, 就返回truereturn true;
}

🗨️ 不能出现连续的红色节点 ⇒ 我们插入节点给个黑色节点多好, 为啥还要给红色节点冒风险呢?


因为, 我们插入的节点颜色是 红色, 插入的位置就有两种可能:

  1. 插入到黑色节点的后面 — — 正常的情况, 不需要进行更新
  2. 插入到红色节点的后面 — — 出现连续的红色节点, 需要 更新这一条支路 (当前节点到祖宗节点这一条路径)中的黑红比例

更新黑红比例的逻辑

由于 插入前, 是符合红黑树的性质,
插入的节点是红色 ⇒ 插入后才会出现连续的红色节点

⇒ 设插入的新节点为 cur(红色) ,
则父亲节点 paren红色, 祖父节点 grandfather黑色 ⇒ 这才符合 插入前符合红黑树的特点, 插入后才会出现连续的红色节点的情况

其实, 更新 当前节点到 祖宗节点这一条路径的 黑红比例 的本质是 看uncle的情况
首先, 要确定 uncle 位于grandfather的哪一侧 && uncle不一定存在, 但parent一定存在
要确定parent 位于 grandfather的那一侧:

  1. parent 位于 grandfather的左侧
  2. parent 位于 grandfather的右侧

其次, 才是 uncle 的存在情况 以及 颜色情况


  1. uncle存在且为红

  2. uncle不存在
    如果 uncle不存在 ⇒ cur为新增节点

  • 如果cur不是新增节点, 那么 parent后面的节点必定是黑色的, 那么就违反了 每一条路径的黑色节点的个数是相同

  1. uncle存在且为黑

    如果uncle存在, 那么必定是 黑色 ⇒ 那么 cur 也应该是 黑色.
    现在看到的cur 是红色的, 是由下面的更新上来的


通过上面的图示, 我们得出 : 插入时, uncle主要分为两种情况

  1. uncle存在且为红 — — 由于更新后的头结点为红 ⇒ 我们需要继续向上更新下去
  2. uncle不存在 或 uncle存在且为黑 — — 由于更新后的头结点为黑 ⇒ 我们不需要继续向上更新下去

insert的完整代码

bool Insert(const pair<K, V> kv)
{if (_root == nullptr){// 根节点是黑色的_root = new Node(kv);_root->_color = BLACK;return true;}Node* parent = _root;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 新建一个节点, 默认是红色cur = new Node(kv);cur->_color = RED;// 链接cur 和 parentif (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更改黑红比例// 父亲节点存在且为红, 才有机会继续向上更新下去while (parent && parent->_color == RED){Node* grandfather = parent->_parent;// parent 为 grandfather的左侧if (grandfather->_left == parent){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_color == RED){// 颜色变化grandfather->_color = RED;parent->_color = uncle->_color = BLACK;// 继续向上调整cur = grandfather;parent = cur->_parent;}else // u不存在 或 u存在且为黑色{if (cur == parent->_left){RotateR(grandfather);grandfather->_color = RED;parent->_color = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}// 更新后的头节点为黑色, 不需要继续向上更新break;}}// parent 为 grandfather的右侧else if (grandfather->_right == parent){Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_color == RED){// 颜色变化grandfather->_color = RED;uncle->_color = parent->_color = BLACK;// 继续向上调整cur = grandfather;parent = cur->_parent;}// u不存在 或 u存在且为黑色else{if (parent->_right == cur){RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}// 更新后的头节点为黑色, 不需要继续向上更新break;}}else{assert("黑红比例失控!");}}// 有可能更新过程中会把 root更新为红色 // && root节点的颜色必须为黑色// -->暴力统一处理根节点的颜色_root->_color = BLACK;return true;
}

insert的验证

  1. 每一条路径的 黑节点个数相同 先找一个 基准值(root的左子树中 黑节点的个数)
    如果后面的路径中 有的黑节点的个数 跟 基准值不同, 那就返回false.
  2. 不能有连续的红节点 ⇒ 当前节点为红节点, 那么父亲节点不能为红节点
  3. root 节点的颜色要为 黑色

验证代码

// 外面调用接口
bool IsBalance()
{return IsBalance(_root);
}bool IsBalance(Node* root)
{if (root == nullptr)return true;// root节点为红, 就直接返回falseif (root->_color != BLACK){return false;}// 基准值 -- root左子树中的黑节点个数int benchmark = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)++benchmark;cur = cur->_left;}// 检查每条路径中黑节点个数 && 不能出现连续的红节点return CheckColour(root, 0, benchmark);
}bool CheckColour(Node* root, int blacknum, int benchmark)
{// 到叶子节点, 比较路径中黑节点的个数 和 基准值if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_color == BLACK){++blacknum;}// 不能存在连续的红节点if (root->_color == RED && root->_parent && root->_parent->_color == RED){cout << root->_kv.first << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);
}

Height

// 外面调用接口
int Height()
{return Height(_root);
}int Height(Node* root)
{if (root == nullptr)return 0;int left = Height(root->_left);int right = Height(root->_right);return left > right ? left + 1 : right + 1;
}

GetRotateCount

int GetRoateCount()
{return RotateCount;
}

测试程序

void rbt_test()
{const int N = 10000000;vector<int> v;v.reserve(N);srand((unsigned int)time(NULL));for (size_t i = 0; i < N; i++){int ret = rand();v.push_back(ret);// v.push_back(i);}muyu::RBTree<int, int> rbt;for (auto e : v){rbt.Insert(make_pair(e, e));// cout << "Insert:" << e << "->" << avl.Isbalance() << endl;}cout << "红黑树是否达标-> " << rbt.IsBalance() << endl;cout << "红黑树的高度-> " << rbt.Height() << endl;cout << "红黑树旋转的次数-> " << rbt.GetRoateCount() << endl;
}int main()
{rbt_test();return 0;
}

运行结果:

红黑树是否达标-> 1
红黑树的高度-> 19
红黑树旋转的次数-> 19119

源码

#pragma once#include<iostream>using namespace std;namespace muyu
{// 枚举enum Color{RED,BLACK};template<class K, class V>struct RBTreeNode{public:RBTreeNode(const pair<K, V> kv):_kv(kv){}public:pair<K, V> _kv;Color _color = BLACK;RBTreeNode<K, V>* _left = nullptr;RBTreeNode<K, V>* _right = nullptr;RBTreeNode<K, V>* _parent = nullptr;};template<class K, class V>class RBTree{typedef RBTreeNode<K, V> Node;public:RBTree(){}void RotateL(Node* parent){++RotateCount;Node* cur = parent->_right;Node* grandfather = parent->_parent;Node* curleft = cur->_left;// 旋转核心parent->_right = curleft;cur->_left = parent;// 更新父亲// 1. parent && curleftif (curleft){curleft->_parent = parent;}parent->_parent = cur;// 2.更新curif (grandfather == nullptr){cur->_parent = nullptr;_root = cur;}else{if (grandfather->_left == parent){grandfather->_left = cur;}else{grandfather->_right = cur;}cur->_parent = grandfather;}}void RotateR(Node* parent){++RotateCount;Node* cur = parent->_left;Node* grandfather = parent->_parent;Node* curright = cur->_right;// 旋转核心parent->_left = curright;cur->_right = parent;// 更新链接关系// 1. parent && currightif (curright){curright->_parent = parent;}parent->_parent = cur;// 2.更新curif (grandfather == nullptr){cur->_parent = nullptr;_root = cur;}else{if (grandfather->_left == parent){grandfather->_left = cur;}else{grandfather->_right = cur;}cur->_parent = grandfather;}}bool Insert(const pair<K, V> kv){if (_root == nullptr){// 根节点是黑色的_root = new Node(kv);_root->_color = BLACK;return true;}Node* parent = _root;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 新建一个节点, 默认是红色cur = new Node(kv);cur->_color = RED;// 链接cur 和 parentif (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更改黑红比例while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_color == RED){// 颜色变化grandfather->_color = RED;parent->_color = uncle->_color = BLACK;// 继续向上调整cur = grandfather;parent = cur->_parent;}else // u不存在 或 u存在且为黑色{if (cur == parent->_left){RotateR(grandfather);grandfather->_color = RED;parent->_color = BLACK;}else{RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else if (grandfather->_right == parent){Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_color == RED){// 颜色变化grandfather->_color = RED;uncle->_color = parent->_color = BLACK;// 继续向上调整cur = grandfather;parent = cur->_parent;}// u不存在 或 u存在且为黑色else{if (parent->_right == cur){RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else{assert("黑红比例失控!");}}// 暴力统一处理根节点的颜色_root->_color = BLACK;return true;}int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr)return 0;int left = Height(root->_left);int right = Height(root->_right);return left > right ? left + 1 : right + 1;}bool CheckColour(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark)return false;return true;}if (root->_color == BLACK){++blacknum;}if (root->_color == RED && root->_parent && root->_parent->_color == RED){cout << root->_kv.first << "出现连续红色节点" << endl;return false;}return CheckColour(root->_left, blacknum, benchmark)&& CheckColour(root->_right, blacknum, benchmark);}bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr)return true;if (root->_color != BLACK){return false;}// 基准值int benchmark = 0;Node* cur = _root;while (cur){if (cur->_color == BLACK)++benchmark;cur = cur->_left;}return CheckColour(root, 0, benchmark);}int GetRoateCount(){return RotateCount;}private:Node* _root = nullptr;int RotateCount = 0;};
}

十年磨一剑,霜刃未曾试。
今日把示君,谁有不平事。
— — 贾岛《剑客》

相关文章:

[C++随笔录] 红黑树

红黑树 红黑树的特点红黑树的模拟实现红黑树的底层结构insert的实现实现思路更新黑红比例的逻辑insert的完整代码 insert的验证 源码 红黑树的特点 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red或 Black。…...

C 和 C++ 可变参数介绍

文章目录 前言概念C 的可变参数参数列表 #va_list 4组宏 C 的可变参数参数列表 #va_list 4组宏初始化列表 initializer_list<> 类模板可变参数模板 总结参考资料作者的话 前言 C 和 C 可变参数介绍。 概念 可变&#xff08;长&#xff09;/不定&#xff08;长&#xff…...

【Git】gui图形化界面的使用、ssh协议以及idea集成Git

目录 gui图形化界面的使用 介绍 特点 gui图形的使用 ssh协议 介绍 步骤及概念 ssh协议的使用 配置公钥 idea集成Git idea配置git IDEA安装gitee IDEA中登入Git ​编辑 项目分享 克隆分享的项目 ​编辑 ​编辑 idea上传远程 gui图形化界面的使用 介绍 GUI&#xff08…...

C语言之文件操作(详解版)

不知不觉我们已经学到C语言的文件操作部分了&#xff0c;这部分内容其实很有意思&#xff0c;因为它可以直接把我们代码中的数据写入硬盘&#xff0c;而不是我们关掉这个程序&#xff0c;代码就没有了&#xff0c;让我们开始学习吧&#xff01; 目录 1.为什么使用文件 2.什么…...

解决mac 下 docker-compose 不是命令

docker-compose docker: ‘compose’ is not a docker command #6569 解决方法&#xff1a; mkdir -p /usr/local/lib/docker ln -s /Applications/Docker.app/Contents/Resources/cli-plugins /usr/local/lib/docker/cli-plugins参考&#xff1a; https://github.com/docker/…...

test_sizeof

test_sizeof //结论&#xff1a; // sizeof(arrU8)得到的大小是u8类型数组的 **定义大小**&#xff0c;在 初始化的时候用 // strlen(arrU8)得到的大小是u8类型数组的 **实际大小**&#xff0c;在 复制的时候用 //sizeof((char*)arrU8)&#xff0c;把一个u8 * 转成 char *&…...

100+ Windows运行命令大全,装B高手必备

操作电脑关闭、重启、注销、休眠的命令细则: 用法: shutdown [/i | /l | /s | /sg | /r | /g | /a | /p | /h | /e | /o] [/hybrid] [/soft] [/fw] [/f] [/m \\computer][/t xxx][/d [p|u:]xx:yy [/c "comment"]] 没有参数 显示帮助。这与键入 /? 是一样的。…...

iOS 设置图标和upload包时显示错误

右键-show in finder-AppIcon.appiconset-然后替换图片 然后遇到个问题 就是图片不能有alpha [Xcode]应用图标&#xff1a;ERROR ITMS-90717: “Invalid App Store Icon. The App Store Icon in the asset catalog in x… 具体操作&#xff1a;只需确保【AppIcon】图片集中不…...

软件工程的舞台上,《人月神话》的美学纷飞

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天给大家分享一本书&#xff1a;《人月神话》——软件工程的经典之作。 《人月神话》是一本具有深远影响力的软件工程著作&#xff0c;无论是软件开发者、管理者还是学习软件工程的人士&#xff0c;都能从中获得宝贵的启…...

C现代方法(第19章)笔记——程序设计

文章目录 第19章 程序设计19.1 模块19.1.1 内聚性与耦合性19.1.2 模块的类型 19.2 信息隐藏19.2.1 栈模块 19.3 抽象数据类型19.3.1 封装19.3.2 不完整类型 19.4 栈抽象数据类型19.4.1 为栈抽象数据类型定义接口19.4.2 用定长数组实现栈抽象数据类型19.4.3 改变栈抽象数据类型中…...

Elasticsearch 作为 GenAI 缓存层

作者&#xff1a;JEFF VESTAL&#xff0c;BAHA AZARMI 探索如何将 Elasticsearch 集成为缓存层&#xff0c;通过降低 token 成本和响应时间来优化生成式 AI 性能&#xff0c;这已通过实际测试和实际实施进行了证明。 随着生成式人工智能 (GenAI) 不断革新从客户服务到数据分析…...

FPGA与STM32_FSMC总线通信实验

FPGA与STM32_FSMC总线通信实验 内部存储器IP核的参数设置创建IP核FPGA代码STM32标准库的程序 STM32F407 上自带 FSMC 控制器&#xff0c;通过 FSMC 总线的地址复用模式实现STM32 与 FPGA 之间的通信&#xff0c;FPGA 内部建立 RAM 块&#xff0c;FPGA 桥接 STM32 和 RAM 块&…...

maven配置自定义下载路径,以及阿里云下载镜像

1.配置文件 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.apache.org…...

01.单一职责原则

单一职责原则 概述 简单来说就是一个类只描述一件事&#xff0c; 比如我们熟知的 userDao.java 只负责 用户域功能。如果userDao既操作user表又操作order表&#xff0c;这显然不合理。正确的做法是让orderDao.java去操作order表。 对类来说的&#xff0c;一个类应该只负责一项…...

RT-Thread上部署TinyMaix推理框架,使MCU赋予AI能力

概要 当谈到微控制器(MCU)和人工智能(AI)的结合,我们进入了一个激动人心的领域。传统上,AI应用程序需要大型计算机或云服务器的处理能力,但随着技术的发展,现在可以将AI嵌入到微控制器中。这为嵌入式系统、物联网设备、机器人和各种其他应用开启了新的可能性。 MCU A…...

设计模式 -- 策略模式(Strategy Pattern)

策略模式&#xff1a;一种行为型模式&#xff0c;这些设计模式特别关注对象之间的通信。在策略模式中&#xff0c;我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 介绍 意图&#xff1a;定义一系列的算…...

Spring Boot 集成 ElasticSearch

1 加入依赖 首先创建一个项目&#xff0c;在项目中加入 ES 相关依赖&#xff0c;具体依赖如下所示&#xff1a; <dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.1.0</version&g…...

百度智能云正式上线Python SDK版本并全面开源!

文章目录 1. SDK的优势2. 千帆SDK&#xff1a;快速落地LLM应用3. 如何快速上手千帆SDK3.1 SDK快速启动3.2 SDK进阶指引3.3 通过Langchain接入千帆SDK 4. 开源社区 百度智能云千帆大模型平台再次升级&#xff01;在原有API基础上&#xff0c;百度智能云正式上线Python SDK&#…...

LeetCode(3)删除有序数组中的重复项【数组/字符串】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 26. 删除有序数组中的重复项 1.题目 给你一个 非严格递增排列 的数组 nums &#xff0c;请你** 原地** 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保…...

前端视角中的微信登录

目录 引入 流程介绍 具体实现 引入 本文主要讲解网站应用中微信登录的具体流程是怎么样的&#xff0c;以及作为前端开发人员在这整个流程中的主要任务是什么。 如果想要实现微信登录的功能&#xff0c;需要开发人员到微信开放平台注册相应的账号&#xff0c;进行注册应用&am…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...