【C++】从零到一掌握红黑树:数据结构中的平衡之道

个人主页: 起名字真南的CSDN博客

个人专栏:

  • 【数据结构初阶】 📘 基础数据结构
  • 【C语言】 💻 C语言编程技巧
  • 【C++】 🚀 进阶C++
  • 【OJ题解】 📝 题解精讲

目录

  • 前言
  • 1 红黑树的概念
      • **红黑树的五大性质**
  • 2 红黑树的实现
    • 2.1 红黑树的结构
    • 2.2 红黑树的插入
      • 2.2.1 红黑树插入的大概过程
      • 2.2.2 关于红黑树插入新节点的变色问题
      • 2.2.3 单旋加变色
      • 2.2.4 双旋加变色
      • 2.2.5 具体代码实现
      • 2.2.6 红黑树插入后变色旋转总结
      • 红黑树插入时需要处理的问题
      • 插入调整的基本步骤
      • 插入调整的条件、操作和原因
        • 情况 1:叔叔节点是红色
        • 情况 2:叔叔节点是黑色(或不存在)
          • 子情况 2.1:当前节点是父节点的左子节点(左左情况)
          • 子情况 2.2:当前节点是父节点的右子节点(左右情况)
          • 子情况 2.3:当前节点是父节点的右子节点(右右情况)
          • 子情况 2.4:当前节点是父节点的左子节点(右左情况)
      • 插入调整总结
        • 图解:插入调整过程
    • 2.3 红黑树的查找
    • 2.4 判断是否平衡

前言

红黑树被广泛应用于许多领域,例如 C++ STL 中的 map 和 set,Java 的 TreeMap 和 TreeSet,以及 Linux 内核、数据库索引等。相比其他平衡树,红黑树调整代价更低,尤其适合插入和删除操作频繁的场景。

1 红黑树的概念

红黑树(Red-Black Tree)是一种自平衡二叉搜索树,通过对节点的颜色、结构及调整规则的约束,实现了树的动态平衡。它的主要目的是在插入、删除等操作后,保持树的高度尽可能小,从而保证在最坏情况下的时间复杂度为 ( O(\log N) )。

红黑树的五大性质

  1. 节点颜色:每个节点要么是红色,要么是黑色。
  2. 根节点黑色:根节点必须是黑色。
  3. 红色节点限制:红色节点的子节点必须是黑色(红色节点不能连续相邻,红黑交替)。
  4. 黑高相等:从任意节点到其每个叶子节点的路径中,包含相同数量的黑色节点。
  5. 叶子节点为黑色:所有的叶子节点(空节点)都被视为黑色。

在这里插入图片描述

2 红黑树的实现

我们想要实现红黑树就要像清楚红黑树的构成时候枚举类型Colour,以及节点,和树一起构成的

2.1 红黑树的结构

  • 需要包含关键字段、包括键值对(Key,Value)、左右节点指针,父亲节点指针,以及枚举类型Colour用来记录节点的颜色
  • 示例代码
#include<iostream>
using namespace std;
enum Colour
{
	RED,
	Black
};

template<class K,class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _colour;

	RBTreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}

};
  • 树 的定义

    • 包含节点。还有根节点
  • 示例代码

class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//实现树的各种操作
private:
	Node* _root = nullptr;
};

在这串代码中并没有构造函数,是因为树是由节点和指针构成的,在构造时会调用节点的构造函数,根节点 也给了初始值。


2.2 红黑树的插入

2.2.1 红黑树插入的大概过程

  1. 在插入之前我们首先要了解以下红黑树的规则,也就是要遵循上面提到的五个性质
  2. 因为我们使用的枚举类型,只有两个变量即红色或黑色所以满足第一条
  3. 第二条根节点的颜色必须是黑色,所以我们在插入的过程中首先要进行判断插入的树是否为空树如果为空树则新增节点为黑色
  4. 不能出现两个连续的节点都是红色节点,如果一个节点是红色节点那么它的孩子必须是黑色,
  5. 黑高相等即每一条路径上面的黑色节点数量必须是一致的,所以我们新插入的节点必须是红色节点如果新插入的节点的父亲节点也是红色这里就涉及到我们接下来要讲的变色问题,

2.2.2 关于红黑树插入新节点的变色问题

首先我们要先确定好变色的前置条件就是新增节点是红色节点,并且它的父亲节点也是红色节点违反了红黑树的特性所以我们需要变色。
在接下来的图中用c来表示当前节点,p表示当前节点的父节点,u表示父亲节点的兄弟节点,g表示父亲节点的父亲节点
在这里插入图片描述
变色原因,因为c和p是两个连续的红色节点违反了红黑树的结构所以需要将p和u变成黑色,但是如果变成黑色以后会增加这两条路径上的黑色节点数量所以为了和以前保持一致需要将他们的父亲节点g节点变成红色,如果g节点的父亲节点也是红色则需要继续向上调整,由于情况有很多种所以我们将下面的结构抽象化处理。
在这里我们需要注意u和p一样在变化之前都是红色节点,如果u为黑色节点则c不是新增节点
在这里插入图片描述

2.2.3 单旋加变色

前置条件:出现了两个连续的红色节点并且u节点为黑色或者不存在,这个情况c可能不是新增节点,而是因为上述情况经过向上调整c为他们的g节点由开始的黑色变成了红色,此时如果新增的节点在左侧并且g节点也就是现在的c节点变成了红色此时u为黑色或者不存在,c在u的左侧这个时候需要进行右单旋,相反则需要进行左单旋,旋转以后需要将父亲节点变成黑色,祖父节点变成红色
在这里插入图片描述
在这里插入图片描述

2.2.4 双旋加变色

前置条件:在向上调整的时候出现了连续的红色节点并且叔叔节点为黑色或者不存在(因为如果不存在我们按照红黑树的特性也可以认为他有一个黑色的空结点)并且如果p在g的左侧,c在p的右侧这个时候我们需要先进行以p节点为旋转节点进行左旋在以g节点作为旋转节点进行右旋。
在这里插入图片描述

在这里插入图片描述
变色都是将g节点变成红色并且p节点和u节点变成黑色。

2.2.5 具体代码实现

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_colour = 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);
	//新插入的结点都是红色
	cur->_colour = RED;
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	//父亲是红色,出现了连续的红色节点
	while (parent && parent->_colour == RED)
	{
		Node* grandfarther = parent->_parent;
		//找叔叔节点
		if (grandfarther->_left == parent)
		{
			//叔叔在右边
			Node* uncle = grandfarther->_right;
			if (uncle && uncle->_colour == RED)
			{
				//变色是为了结点的数量不变,并且不能出现连续的红色结点
				parent->_colour = Black;
				uncle->_colour = Black;
				grandfarther->_colour = RED;

				//继续往上处理
				cur = grandfarther;
				parent = cur->_parent;
				//如果cur是根节点,则父亲为空,while循环条件造成了空引用
				//在循环条件中加上不能为空
			}
			else
			{
				if (cur == parent->_left)
				{
					//叔叔节点为黑色或者不存在
					//右单旋
					//对grandfather位置进行旋转
					//      g                p
					//    p    u     ->   c    g  
					//  c                         u
					RotateR(grandfarther);
					parent->_colour = Black;
					grandfarther->_colour = RED;
				}
				else
				{
					//      g               c
					//    p    u    ->   p     g
					//      c                    u
					//需要双旋
					RotateL(parent);
					RotateR(grandfarther);
					cur->_colour = Black;
					grandfarther->_colour = RED;
				}
				break;
			}
		}
		else
		{
			//叔叔在左边
			Node* uncle = grandfarther->_left;

			if (uncle && uncle->_colour == RED)
			{
				//变色是为了结点的数量不变,并且不能出现连续的红色结点
				parent->_colour = Black;
				uncle->_colour = Black;
				grandfarther->_colour = RED;

				//继续往上处理
				cur = grandfarther;
				parent = cur->_parent;
				//如果cur是根节点,则父亲为空,while循环条件造成了空引用
				//在循环条件中加上不能为空

			}
			else
			{
				if (cur == parent->_right)
				{
					//左单旋
					//对grandfather位置进行旋转
					//      g                u
					//    u    p     ->   c    g  
					//  c                         p
					RotateL(grandfarther);
					parent->_colour = Black;
					grandfarther->_colour = RED;
				}
				else
				{
					//      g               c
					//    u    p    ->   u     g
					//      c                    p
					//需要双旋
					RotateR(parent);
					RotateL(grandfarther);
					cur->_colour = Black;
					grandfarther->_colour = RED;
				}
				break;
			}
		}
	}

	//必须保证跟节点是黑色
	_root->_colour = Black;
	return true;
}

void RotateR(Node* parent)
{
	//         p                    subL
	//    subL           ->      c         p 
	//  c     subLR            a       subLR
	//a
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	Node* pParent = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (parent == pParent->_left)
		{
			pParent->_left = subL;
		}
		else
		{
			pParent->_right = subL;
		}
		subL->_parent = pParent;
	}
	
}

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;
	
	Node* pParent = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == pParent->_left)
		{
			pParent->_left = subR;
		}
		else
		{
			pParent->_right = subR;
		}
		subR->_parent = pParent;
	}
}

具体的旋转细节请参考:

【C++】从「树」到「平衡」:全面解密 AVL 树的奥秘与实现

其中详细的介绍了单旋和双旋大家可以作为参考

2.2.6 红黑树插入后变色旋转总结

在红黑树的插入过程中,为了保持红黑树的性质,需要结合旋转和变色操作进行调整。以下是插入过程中各种旋转和变色的前置条件及其原因的详细梳理。


红黑树插入时需要处理的问题

插入新节点可能破坏以下红黑树性质:

  1. 性质 2:根节点必须是黑色。
  2. 性质 4:不能有两个连续的红色节点。
  3. 性质 5:每条从根到叶子节点的路径必须包含相同数量的黑色节点。

因此,调整过程主要是为了修复连续红色节点问题黑高失衡问题


插入调整的基本步骤

  1. 新插入的节点默认为红色(不会破坏黑高平衡)。
  2. 根据父节点和叔叔节点的颜色,可能需要变色和/或旋转调整。

插入调整的条件、操作和原因

情况 1:叔叔节点是红色
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点也是红色。
  • 操作

    • 父节点和叔叔节点变为黑色。
    • 祖父节点变为红色。
    • 将当前节点移动到祖父节点,继续检查祖父节点。
  • 原因

    • 变色的目的是修复性质 3(连续红色节点)。
    • 因为祖父节点变红,可能导致祖父节点和它的父节点连续红色,所以需要继续向上调整。

情况 2:叔叔节点是黑色(或不存在)
子情况 2.1:当前节点是父节点的左子节点(左左情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的左子节点
  • 操作

    • 对祖父节点进行右旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 右旋是为了修复树的平衡,避免左重失衡。
    • 变色是为了满足性质 3 和性质 4。

子情况 2.2:当前节点是父节点的右子节点(左右情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的右子节点
  • 操作

    • 对父节点进行左旋,将问题转化为左左情况。
    • 转换后再对祖父节点进行右旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 左旋是为了将右偏问题化简为左偏问题。
    • 后续的右旋和变色则恢复了树的平衡性和红黑树性质。

子情况 2.3:当前节点是父节点的右子节点(右右情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的右子节点
  • 操作

    • 对祖父节点进行左旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 左旋是为了修复树的平衡,避免右重失衡。
    • 变色是为了满足性质 3 和性质 4。

子情况 2.4:当前节点是父节点的左子节点(右左情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的左子节点
  • 操作

    • 对父节点进行右旋,将问题转化为右右情况。
    • 转换后再对祖父节点进行左旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 右旋是为了将左偏问题化简为右偏问题。
    • 后续的左旋和变色则恢复了树的平衡性和红黑树性质。

插入调整总结

条件操作原因
叔叔节点是红色变色修复连续红色问题,可能需要向上递归调整
叔叔节点是黑色,左左情况右旋 + 变色修复左重失衡和连续红色问题
叔叔节点是黑色,左右情况左旋 + 右旋 + 变色转化为左左情况后再修复
叔叔节点是黑色,右右情况左旋 + 变色修复右重失衡和连续红色问题
叔叔节点是黑色,右左情况右旋 + 左旋 + 变色转化为右右情况后再修复

图解:插入调整过程

左左情况

插入前:
        50(B)
       /
     30(R)
    /
  20(R)

右旋 + 变色后:
        30(B)
       /    \
     20(R)  50(R)

右右情况

插入前:
     20(B)
       \
       30(R)
         \
         50(R)

左旋 + 变色后:
       30(B)
      /    \
    20(R)  50(R)

左右情况

插入前:
        50(B)
       /
     20(R)
       \
       30(R)

左旋后:
        50(B)
       /
     30(R)
     /
   20(R)

右旋 + 变色后:
        30(B)
       /    \
     20(R)  50(R)

右左情况

插入前:
       20(B)
         \
         50(R)
        /
      30(R)

右旋后:
       20(B)
         \
         30(R)
            \
            50(R)

左旋 + 变色后:
       30(B)
      /    \
    20(R)  50(R)

原因总结

  1. 变色:解决颜色冲突(连续红色节点)。
  2. 旋转:解决树的结构失衡(左重或右重)。
  3. 结合变色与旋转:保证红黑树的五条性质不被破坏。

2.3 红黑树的查找

按照二叉树的旋转逻辑即可:

Node* find(const K& k)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < k)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > k)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

2.4 判断是否平衡

中心思想就是判断每个路径上的黑色节点数量是否相等,为了方便我们定义一个参考值用来记录其中一条路径上的黑色节点,然后再遍历其他路径和该路径的参考值进行比较,如果与参考值不相等则不平衡

bool IsBalance()
{
	if (_root == nullptr)
	{
		return true;
	}
	if (_root->_colour == RED)
	{
		return false;
	}

	//参考值
	int refnum = 0;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_colour == Black)
		{
			refnum++;
		}
		cur = cur->_left;
	}
	return check(_root, 0, refnum);
}

bool check(Node* root, int blacknum, const int refnum)
{
	if (root == nullptr)
	{
		//前序遍历走到空了,意味着已经走完一条路径
		//和参考值进行比较
		if (blacknum == refnum)
		{
			return true;
		}
		else
		{
			cout << "黑色节点数量不匹配" << endl;
			return false;
		}
	}
	// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
	if (root->_colour == RED && root->_parent->_colour == RED)
	{
		cout << root->_kv.first << "存在连续的红色结点" << endl;
		return false;
	}
	if (root->_colour == Black)
	{
		blacknum++;
	}
	return check(root->_left, blacknum, refnum)
		&& check(root->_right, blacknum, refnum);
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

webpack(react)基本构建

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它的主要功能是将各种资源&#xff08;如 JavaScript、CSS、图片等&#xff09;视为模块&#xff0c;并将它们打包成一个或多个输出文件&#xff0c;以便…

C++STL(四)-->vector 的模拟实现

1.vector的各函数接口&#xff1a; namespace cl {//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, cons…

机器学习实战:泰坦尼克号乘客生存率预测(数据处理+特征工程+建模预测)

项目描述 任务&#xff1a;根据训练集数据中的数据预测泰坦尼克号上哪些乘客能生存下来 数据源&#xff1a;csv文件&#xff08;train.csv&#xff09; 目标变量&#xff1a;Survived&#xff08;0-1变量&#xff09; 数据集预览&#xff1a; 1、英文描述&#xff1a; 2、…

MATLAB不动点迭代法求单变量非线性方程的根程序加实例

不动点迭代法用于单变量线性方程近似根&#xff0c;首先确定一个方程根附近的近似初始值&#xff0c;采用逐次逼近的方法&#xff0c;使用迭代公式不断地更新这个初始值&#xff0c;使这个初始值不断趋近于准确值。 1.不动点迭代法自定义函数 fixed_point.m是一个MATLAB函数&a…

BurpSuite使用篇--抓包方法与解码器

BurpSuite是一个web渗透利器&#xff0c;可以抓包改包也可以扫描漏洞&#xff0c;将漏洞扫描和利用集成化一体&#xff0c;更可以支持外部插件拓展&#xff0c;非常牛的工具。 那我们怎么使用呢&#xff1f; 使用BurpSuite 老版本中BurpSuite需要在浏览器中配置本地代理&…

【查询目录】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

ESP32-S3模组上跑通ES8388(8)

接前一篇文章&#xff1a;ESP32-S3模组上跑通ES8388&#xff08;7&#xff09; 二、利用ESP-ADF操作ES8388 2. 详细解析 上一回继续解析到了ESP-ADF的audio_hal层的第1个也是最为关键的函数 —— audio_hal_init()中的第5段代码&#xff0c;也就是mutex_lock函数与mutex_unlo…

bind实验

服务端 查看域名 [rootclient yum.repos.d]# hostname client 设置域名 [rootclient yum.repos.d]# hostnamectl set-hostname dns1.openlab.edu [rootclient yum.repos.d]# cd [rootclient ~]# hostname dns1.openlab.edu 安装bind包 [rootclient ~]# yum install bind -y…

【LeetCode每日一题】——717.1比特与2比特字符

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 简单 三【题目编号】 717.1比特与2比特字符 四【题目描述】 有两种特…

Linux学习笔记11 系统启动初始化,服务和进程管理(下)

前文 前文介绍了系统启动初始化程序&#xff0c;介绍了systemd的基础知识。这里主要看一下我们systemd的单元管理和常用的命令以及示例。 Linux学习笔记10 系统启动初始化&#xff0c;服务和进程管理&#xff08;上&#xff09;-CSDN博客 systemd单元管理 启动服务 这很常…

pnpm安装electron出现postinstall$ node install.js报错

pnpm install --registryhttp://registry.npm.taobao.org安装依赖包的时候出现了postinstall$ node install.js报错 找到install.js 找到downloadArtifact方法&#xff0c;添加如下代码 mirrorOptions:{mirror:"http://npmmirror.com/mirrors/electron/"}http://n…

程序执行堆栈执行模拟

所有的文件都是在硬盘&#xff08;磁盘&#xff09;上&#xff0c;调用时先调用javac指令的jdk编译成.class然后被java指令的jre送到内存中&#xff0c;java在内存中有自己的一片区域叫JVM&#xff0c;编译进来的文件首先进入方法区。 staitc的属性就是在进入内存的时候开辟了一…

windows平台使用C#创建系统服务

使用 C# 在 Windows 平台创建和管理系统服务 在 Windows 平台上&#xff0c;系统服务&#xff08;Windows Service&#xff09;是一种运行在后台、无需用户交互的应用程序。系统服务广泛应用于长期任务处理、网络监听、后台调度等场景。本文将详细介绍如何使用 C# 创建一个 Win…

JVM_总结详解

1、CPU和内存的交互 了解jvm内存模型前&#xff0c;了解下cpu和计算机内存的交互情况。【因为Java虚拟机内存模型定义的访问操作与计算机十分相似】 有篇很棒的文章&#xff0c;从cpu讲到内存模型:[什么是java内存模型&#xff1f;] 在计算机中&#xff0c;cpu和内存的交互最…

TLinux 3.1 (Centos 8)修改网卡设备名

原网卡设备名&#xff1a;ifcfg-enp17s 新网卡设备名&#xff1a;ifcfg-eth0 1. 修改接口配置文件名 rename /etc/sysconfig/network-scripts/ifcfg-enp17s /etc/sysconfig/network-scripts/ifcfg-eth0 2. 修改接口配置文件 vi /etc/sysconfig/network-scripts/ifcfg-eth…

ElasticSearch7.x入门教程之全文搜索(七)

文章目录 前言一、多条件查询&#xff1a;bool query二、更加精准查询&#xff1a;dis_max query总结 前言 这里再接着上一篇文章继续记录。非常感谢江南一点雨松哥的文章。 欢迎大家去查看&#xff0c;地址&#xff1a;http://www.javaboy.org 一、多条件查询&#xff1a;boo…

基于springboot 的体质测试数据分析及可视化设计LWPPT

技术可行性&#xff1a;技术背景 本企业网站在Windows操作系统中进行开发&#xff0c;并且目前PC机的性能已经可以胜任普通网站的web服务器。系统开发所使用的技术也都是自身所具有的&#xff0c;也是当下广泛应用的技术之一。 系统的开发环境和配置都是可以自行安装的&#x…

oracle小技巧-解决特殊密码字符而导致的exp错误

在使用oracle数据库的时候&#xff0c;我们经常会利用exp工具对某些表进行导出。但有些时候&#xff0c;因我们用户密码为安全性设有特殊字符&#xff0c;导致exp导出时候报&#xff1a;“EXP-00056和ORA-12154”&#xff0c;今天我们就分享下如何通过设置符号隔离的小技巧解决…

【ArcGIS Pro实操第11期】经纬度数据转化成平面坐标数据

经纬度数据转化成平面坐标数据 数据准备ArcGIS操作步骤-投影转换为 Sinusoidal1 投影2 计算几何Python 示例 另&#xff1a;Sinusoidal (World) 和 Sinusoidal (Sphere) 的主要区别参考 数据准备 数据投影&#xff1a; 目标投影&#xff1a;与MODIS数据相同&#xff08;Sinu…

病理组学分析系列教程1:使用Python-histolab进行病理图像预处理,tiles切片自动生成

首先&#xff0c;先上参考资料链接&#xff1a; 了解数字病理影像图片WSI如何切割-使用histolab 视频详细讲解使用histolab进行病理图像切分 详细介绍数字病理图像的存储格式及格式转换工具介绍 一、病理图像预处理 相信很多朋友对于WSI这种巨大的病理图像的预处理都觉得束…