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

【C++】详解红黑树并模拟实现

前言:

       上篇文章我们一起学习了AVL树比模拟实现,我们发现AVL树成功地把时间复杂度降低到了O(logN)。但是同时我们不难发现一个问题,在构建AVL树中我们也付出了不小的代价,频繁的旋转操作导致效率变低。为了解决这个问题,我们本章将迎来更为实用的红黑树,他在提高查找效率的同时也相对AVL树提高了构建树的效率,他的应用将更加广泛(比如map、set的底层实现就是红黑树)!

目录

(一)红黑树的概念

1、概念

2、特性

(二)红黑树的模拟实现

1、结点的定义

2、结点的查找实现(Find)

3、结点的插入实现(Insert)*重点

3.1前序问题

3.2具体流程

情况一:叔叔存在且为红——变色处理

情况二:叔叔不存在or叔叔存在且为黑——旋转+变色

 情况三:叔叔不存在or叔叔存在且为黑——双旋+变色(区别于情况二)

4、具体代码(Find+Insert)

(三)验证红黑树

1、中序遍历

2、验证红黑树几大特性

3、测试用例

(四)项目完整代码


(一)红黑树的概念

1、概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black,所以叫红黑树

通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

2、特性

  • 每个结点不是红色就是黑色
  • 根节点是黑色的 
  • 如果一个节点是红色的,则它的两个孩子结点是黑色的 
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点
个数的两倍?
这里我们从路径最短的情况和最长的情况分析:
我们假设该红黑树每条路径黑色结点数是3
路径最短的情况:
这条路径没有红色结点,如下图:

路径最长的情况:
这条路径一个黑色节点接着一个红色节点,交相出现:

这时我们发现最短路径时,一条路径上有三个结点(情况一);最长路径时,一条路径上有五个节点(情况二)。所以可以保证其最长路径中节点个数不会超过最短路径节点个数的两倍。

(二)红黑树的模拟实现

1、结点的定义

我们还是沿用AVL树节点的三叉链定义方法,颜色我们用枚举来实现:

ps:这里一开始结点定义为红色的原因,我们在后文插入的时候会详细讲解。

2、结点的查找实现(Find)

在红黑树中的Find(查找)操作和之前二叉搜索树和AVL树实现方法一样,这里我们简单带过。

结点存的值比当前结点小就向左找,比当前结点大就向右找,最后遇到空节点表示没找到。

3、结点的插入实现(Insert)*重点

3.1前序问题

首先,我们解答上面留下的那个问题,

为什么新插入的结点要默认给红色的?

  • 假设我们默认给新增的结点黑色,那么一定违反上面红黑树的特性4(对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 )。这样我们还要对其他路径进行修改黑色结点的数量。
  • 但是如果默认给红色,我们则是可能违反特性3(如果一个节点是红色的,则它的两个孩子结点是黑色的 )。为什么说是可能,因为如果他的父亲节点是黑色则不违反。就算违反了。每条路径黑色结点的数量还是一样的,我们只需要调整所在的那一条路径即可,代价相对小了许多。

3.2具体流程

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

(分别对应parent,grandfather,uncle的意思)

插入几大流程:

  • 按照搜索二叉树的查找方法,找到要插入的位置
  • 将新增结点插入到待插入位置
  • 如果需要调整则调整红黑树

其中需不需要调整主要是看叔叔颜色的情况。

下面具体分析:

我们先给出一个基本的图示

这里的三角形就相当于AVL树中的小长方形,代表了一部分子树。

情况一:叔叔存在且为红——变色处理

  • cur为红,p为红,g为黑, u存在且为红

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

首先我们知道,红黑树调整主要看叔叔,第一步我们将父亲变成黑色,为了维护性质4,我们也要将叔叔的颜色变成黑色,同时也要将祖父结点的颜色变成红色,因为我们此时处理的不一定是整棵树,有可能是某个子树,如果此时不是子树,就需要将根节点变成黑色。这样我们就在局部调整颜色并保证了不破坏规则。

 

调整的部分可能是一整棵树,也可能是某一棵子树

  • 如果g是根节点,调整完成后,需要将g改为黑色
  • 如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整

 

情况一的情景下,我们只关心颜色的调整就可以在不破坏规则的情况下插入结点!


情况二:叔叔不存在or叔叔存在且为黑——旋转+变色

情况二可能就是从情况一调整变化得到的情况。

具体情况 1

当叔叔不存在时:

我们调整颜色总会破坏规则,此时结合上面长路径不会超过短路径二倍的特性我们分析,英爱使用到了和AVL树相似的旋转处理。

  • 此时是左边高了,我们对图示树进行右单旋,这里的旋转和AVL树中旋转的方式如出一辙,可以拿过来直接复用。
  • 同样的道理,如果是右边高了,我们就可以进行左单旋,旋转的方法也和AVL树中的如出一辙,也可拿过来直接复用。(这里我们就不像AVL树那样详细解释了,实现方法是一样的,如果读者不太了解可以看上一篇文章)

 具体情况 2

当叔叔存在且为黑时:

按照上文说明,这种情况必定是由情况一变化而来的。

此时也是旋转+变色

这样子才能保护好红黑树的结构。


 情况三:叔叔不存在or叔叔存在且为黑——双旋+变色(区别于情况二)

同样是 叔叔不存在 or 叔叔存在且为黑,为啥分为情况二和情况三呢?

上述旋转的情况都是单边的情况,也就是说,cur、p、g在一条线上的情况,若是出现了这三者不在一条直线的时候,单旋就解决不了问题了。

区别:

  • p是g的左,cur是p的左,那么是情况二 —— 单旋
  • p是g的左,cur是p的右,那么是情况三 —— 双旋

此时我们就引入了双旋

(这里我们可以结合上一篇AVL树来区别单旋和双旋)

  • 先以p为旋转点再以g为旋转点
  • 先变到情况二的样子
  • 再根据情况二来继续变

其实看完情况二和三有些人就会有疑问了,看上去每条路径上黑色结点树不一样啊,为什么要这样旋转变色?

再此之前我们推测过当叔叔存在且为黑时,一定是由情况一变来的,所以cur一开始是黑的,这个树并不违反性质,子树由情况一变化之后的子树结点的颜色也相应变化了,只是没有显示出来而已。

4、具体代码(Find+Insert)

Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;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 (parent->_kv.first>kv.first){parent->_left = cur;}else if (parent->_kv.first < kv.first){parent->_right = cur;}cur->_parent = parent;//调整颜色while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent)//u存在且为红{Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//u不存在或者为黑,旋转加变色{if (cur == parent->_left){//    g//  p   u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//grandfather->_right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//     g//  u    p//          cuncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else// :u不存在/u存在且为黑,旋转+变色{if (cur == parent->_right){//     g//  u    p//          cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//      g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}

(三)验证红黑树

老样子,我们在上一章模拟实现AVL树后用了几个函数来验证我们构建的树是否是AVL树,这里我们也要验证一下我们构建的这棵树是否是红黑树。

几个验证要点:

  • 这棵树是否是二叉搜索树
  • 根结点是否是黑色结点
  • 红色结点的子结点是否是黑色结点
  • 每条路径黑色结点树是否一样

满足这几个要点,也就是红黑树的特性,那么这棵树1就是红黑树了。

1、中序遍历

一棵树是红黑树的前提就是他必须是二叉搜索树,二叉搜索树的特点就是中序遍历是有序的。

void InOrder(){return _InOrder(_root);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}

ps:为了符合用户的使用习惯,我们还是采取嵌套的方式,大家不明白的可以看一下上一篇文章验证处有说明哦!

2、验证红黑树几大特性

相对AVL树,验证红黑树是更加麻烦的,几个要点:

  • 首先判断根结点是否是黑色
  • 判断每条路径黑色结点数我们思路是先取一条路径黑色结点数量为基准值,然后递归统计其他路径的数目,遇到空结点再比较
  • 遇到红色结点我们如果判断他的孩子结点是否是黑色,如果是要跳过(代码不好写),不如换个思路,遇到一个红色结点就判断双亲是否是红色,如果是(存在连续红色结点)返回false。

bool IsBalance(){if (_root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if(cur->_col==BLACK)benchmark++;cur = cur->_left;}return _Check(_root, 0, benchmark);}private:bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}

通过递归的方式将每条支路都走了一遍和基准值比较,并且将红黑树的性质全都验证了。

3、测试用例


void Test_RBTree1()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t1;for (auto e : a){/*	if (e == 14){int x = 0;}*/t1.Insert(make_pair(e, e));//cout << e << "插入:" << t1.IsBalance() << endl;}t1.InOrder();cout << endl;cout << t1.IsBalance() << endl;
}void Test_RBTree2()
{srand(time(0));const size_t N = 5000000;RBTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(make_pair(x, x));//cout << t.IsBalance() << endl;}//t.Inorder();cout << t.IsBalance() << endl;cout << t.Height() << endl;
}

精检验,没有问题:

(四)项目完整代码

#pragma once
#include<iostream>
#include<assert.h>using namespace std;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){}
};template<class K,class V>
struct RBTree
{typedef RBTreeNode<K, V> Node;void Destroy(Node* root){_Destroy(root);root = nullptr;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;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 (parent->_kv.first>kv.first){parent->_left = cur;}else if (parent->_kv.first < kv.first){parent->_right = cur;}cur->_parent = parent;//调整颜色while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent)//u存在且为红{Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else//u不存在或者为黑,旋转加变色{if (cur == parent->_left){//    g//  p   u//cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p   u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else//grandfather->_right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//     g//  u    p//          cuncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else// :u不存在/u存在且为黑,旋转+变色{if (cur == parent->_right){//     g//  u    p//          cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//      g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void InOrder(){return _InOrder(_root);}int Height(){return _Height(_root);}bool IsBalance(){if (_root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if(cur->_col==BLACK)benchmark++;cur = cur->_left;}return _Check(_root, 0, benchmark);}private:bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}int _Height(Node* root){if(root==nullptr){return 0;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}void _Destroy(Node* root){if (root == nullptr)return;_Destroy(root->_left);_Destroy(root->_right);delete root;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){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;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}
private:Node* _root=nullptr;
};void Test_RBTree1()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };RBTree<int, int> t1;for (auto e : a){/*	if (e == 14){int x = 0;}*/t1.Insert(make_pair(e, e));//cout << e << "插入:" << t1.IsBalance() << endl;}t1.InOrder();cout << endl;cout << t1.IsBalance() << endl;
}void Test_RBTree2()
{srand(time(0));const size_t N = 5000000;RBTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(make_pair(x, x));//cout << t.IsBalance() << endl;}//t.Inorder();cout << t.IsBalance() << endl;cout << t.Height() << endl;
}

祝您学业有成!

相关文章:

【C++】详解红黑树并模拟实现

前言&#xff1a; 上篇文章我们一起学习了AVL树比模拟实现&#xff0c;我们发现AVL树成功地把时间复杂度降低到了O(logN)。但是同时我们不难发现一个问题&#xff0c;在构建AVL树中我们也付出了不小的代价&#xff0c;频繁的旋转操作导致效率变低。为了解决这个问题&#xff0c…...

Matlab图像处理-最大类间方差阈值选择法(Otsu)

基本思想 最大类间方差阈值选择法又称为Otsu 算法&#xff0c;该算法是在灰度直方图的基础上用最小二乘法原理推导出来的&#xff0c;具有统计意义上的最佳分割阈值。它的基本原理是以最佳阈值将图像的灰度直方图分割成两部分&#xff0c;使两部分之间的方差取得最大值&#x…...

Spring Cloud(Finchley版本)系列教程(三) 服务消费者(Feign)

Spring Cloud(Finchley版本)系列教程(三) 服务消费者(Feign) 一、Feign和OpenFeign的对比 Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。OpenFeign是SpringCloud自己研发的,在Feign的基础上支…...

AI图片生成 discord 使用midjourney

参考: 不用找咒语了&#xff01;Midjourney图生文功能特征解析&#xff0c;玩转Describe命令&#xff0c;快速搞定AI绘画_哔哩哔哩_bilibili 1 登录 discord 2 点发现 找 midjourney 3 创建 服务器 -> 亲自创建 4 选 仅供我和我的朋友使用 5 起个 服务器名字 6 加bot 由于…...

gitlab 点击Integrations出现500错误

背景&#xff1a;在新服务器重新搭建了gitlab&#xff0c;并导入原来gitlab的备份&#xff0c;在项目中点击点击Integrations出现500错误。 解决方法&#xff1a;1.进入新服务器&#xff0c;将 /etc/gitlab/gitlab-secrets.json重命名为 /etc/gitlab/gitlab-secrets.json.bak …...

【2023高教社杯】A题 定日镜场的优化设计 问题分析及数学模型

【2023高教社杯】A题 定日镜场的优化设计 问题分析及数学模型 1 题目 构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。 定日镜是塔式太阳能光热发电站&#xff08;…...

rac异常hang死故障分析(sskgxpsnd2)

x86虚拟化的平台麒麟系统的一套RAC。事件梳理20:24左右&#xff0c;发现一个节点hang死&#xff0c;关闭操作没有响应。关闭hang死节点&#xff0c;另一个节点也发生hang死&#xff0c;然后重启了另一个节点。 无效分析部分 检查gi的alert日志 有一个很大跨度的时间回退 再看…...

2023.9.7 关于 TCP / IP 的基本认知

目录 网络协议分层 TCP/IP 五层&#xff08;四层&#xff09;模型 应用层 传输层 网络层&#xff08;互联网层&#xff09; 数据链路层&#xff08;网络接口层&#xff09; 物理层 网络数据传输的基本流程 网络协议分层 为什么需要分层&#xff1f; 分层之后&#xff0c…...

Python 图片处理

Step1 提取PDF中的图片&#xff0c;并另存 Step2 去除灰色纸张背景 import PyPDF2 from PIL import ImageEnhance,Image,ImageFilter import cv2 import numpy as np from skimage.filters import unsharp_mask from skimage.filters import gaussian from skimage.restora…...

信道估计 | 信道

文章目录 定义分类LS 估计MMSE估计LS vs MMSE 定义 从接收数据中将假定的某个信道模型参数估计出来的过程&#xff0c;如果信道是线性的&#xff0c;信道估计是对系统的冲击响应进行估计&#xff0c;需强调的是&#xff0c;信道估计是信道对输入信号影响的一种数学表示&#x…...

腾讯发布超千亿参数规模的混元大模型;深度学习与音乐分析与生成课程介绍

&#x1f989; AI新闻 &#x1f680; 腾讯发布超千亿参数规模的混元大模型 摘要&#xff1a;腾讯在2023腾讯全球数字生态大会上发布混元大模型&#xff0c;该模型拥有超千亿的参数规模和超2万亿 tokens 的预训练语料。混元大模型将支持多轮对话、内容创作、逻辑推理、知识增强…...

[html]当网站搭建、维护的时候,你会放个什么界面?

效果图&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>网站建设中</title><style>/* 基础样式 */body, html {margin: 0;padding: 0;height: 100%;font-family: Arial, sa…...

javaee spring aop 的五种通知方式

spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns:aop"http://www.springframework.…...

【Redis】3、Redis主从复制、哨兵、集群

Redis主从复制 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xff0c;只能由主节点到从节点。 默认情况下&#xff0c;每台Redis服务器…...

vcpkg方式安装zlmediakit

主要参考: https://github.com/ZLMediaKit/ZLMediaKit/wiki/vcpkg%E6%96%B9%E5%BC%8F%E5%AE%89%E8%A3%85zlmediakit vcpkg的相关配置不在本文论述。很多库或源码下载不下来&#xff0c;通过第三方下载后放在download目录下&#xff0c;名称要和vcpkg期望的一致。可通过云服务器…...

【大数据】基于 Flink CDC 高效构建入湖通道

基于 Flink CDC 高效构建入湖通道 1.Flink CDC 核心技术解析2.CDC 数据入湖入仓的挑战2.1 CDC 数据入湖架构2.2 CDC 数据 ETL 架构 3.基于 Flink CDC 的入湖入仓方案3.1 Flink CDC 入湖入仓架构3.2 Flink CDC ETL 分析3.3 存储友好的写入设计3.4 Flink CDC 实现异构数据源集成3…...

微信小程序开发---网络数据请求

目录 一、小程序中网络数据请求的限制 二、发起get请求 三、发起post请求 一、小程序中网络数据请求的限制 具体有两个限制&#xff1a; &#xff08;1&#xff09;只能请求HTTPS类型的接口 &#xff08;2&#xff09;必须将接口的域名添加到信任列表中&#xff0c;在调试的时…...

vulkan学习路径

1.学习路径 了解图形渲染基础知识&#xff1a; 学习计算机图形学基础概念&#xff0c;包括坐标系统、三角形渲染、光照模型等。可以参考经典的图形学教材&#xff0c;如《Real-Time Rendering》和《Computer Graphics: Principles and Practice》。了解图形API的发展历史&#…...

NIFI使用InvokeHTTP发送http请求

说明 这里介绍四种平时常用的http请求方法&#xff1a;GET、POST、PUT、DELETE。 在官方的介绍文档中关于InvokeHTTP处理器的描述是这么说的&#xff1a; An HTTP client processor which can interact with a configurable HTTP Endpoint. The destination URL and HTTP Met…...

Spire.xls+excel文件实现单据打印

报表和单据打印&#xff0c;通常都是使用fastreport之类的&#xff0c;因为有了现成的xls模板样式&#xff0c;如果转成fastreport那还需要花时间&#xff0c;是用spire.xls这个玩意简单&#xff0c;超好用。 一.引用 using Spire.Xls; 二.基本的操作 // 创建工作簿&#xff…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...