C++ 红黑树插入详解

前言

        在之前,我们学习了AVL树,知道了AVL树是一个平衡二叉搜索树,如果没学过AVL树,这篇文章看起来会很吃力,不清楚如何旋转的,建议可以先看AVL树的内容。

        今天我们要学习的红黑树,他也是一颗平衡二叉搜索树,只不过他不像AVL树那样是绝对平衡的二叉搜索树,而是一种近视平衡,是不是知道这一点后,感觉红黑树也没有那么神秘了。

目录

一、红黑树概念

二、红黑树性质

三、红黑树插入结点

1.红黑树的结点

2.找到应该插入的位置

3.插入结点

4.判断是否需要处理

4.1叔叔存在且为红色

4.2叔叔不存在或者存在是黑色

4.2.1叔叔不存在

4.2.2叔叔存在且为黑 

4.2.3总结

四、判断是否为红黑树

五、中序遍历

六、总代码


一、红黑树概念

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

二、红黑树性质

1. 每个结点不是红色就是黑色

2. 根节点是黑色的

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

第1点和第2点都很好理解。不是红就是黑,根为黑。

对于第3点:“如果一个节点是红色,那么它的两个孩子的结点是黑色的”,这句话我们可以翻译一下,他代表着不可能出现两个连续的红色结点

对于第4点,从某一个结点开始到其所有后代叶节点的路径上,黑色结点的数量相同。这里指的叶子结点是空节点

如下左图,一共有7条路径。是红黑色       下右图,最右路径只有两个黑色结点,不是红黑树

  • 那么,有了这几点性质,为何就能保证我们概念里所说到的最长路径不超过最短路径的两倍呢? 

因为,最长路径为一黑一红(从第3点可知,不能有连续的红色结点),最短路径为全为黑色(因为有了红色结点就会增加长度)。并且由第4点可知,每一条路径上的黑色结点都相同,如果每条路径上的黑色节点为n个,那么最长路径可以为2n,最短路径可以为n。这样就可以保证最长路径不超过最短路径的两倍。

由此我们可以发现,红黑树的查找效率并不会比AVL树差很多,他们的查找时间复杂度都是log(n) ,只是常数的差距。那么何为Map和Set的底层要用红黑树而不是AVL树呢?因为红黑树查找效率不差,插入效率会高一些,(没有严格的高度差,首先考虑变色,不得已才旋转)。

三、红黑树插入结点

1.红黑树的结点

红黑树的结点也要用到三叉链,就是有左右子树结点,还有父亲节点,还有一个_key值存储数据,和一个_col存放结点是什么颜色(_col用了枚举来方便我们管理)

enum color
{
	RED,
	BLACK
};

template<class K>
struct RBTreeNode
{
	RBTreeNode<K>* _parent;
	RBTreeNode<K>* _left;
	RBTreeNode<K>* _right;
	K _key;
	color _col;
	RBTreeNode(const K& key)
		:_key(key)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(RED)
	{}
};

对于RBTreeNode的构造函数,_key、_parent、_left、_right的初始化没问题,也一直都能理解,那为什么要将结点的颜色初始化为红色呢? 黑色不行吗

这也要涉及到红黑树的性质3和4

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 

如果新节点为红色,我们可能就会破坏掉第3条性质,如果新节点的父亲为黑,则不需要处理,如果父亲为红,才需要处理。并且我们只会破坏当前路径的规则,对于其他路径并不涉及

如果新节点为黑色,我们会破坏掉第4条的性质,使得每条路径的黑色结点不同,似乎是对整棵树都会有影响,这样非常不方便我们处理。

因此在选择新结点颜色为红(得罪一个)还是为黑(得罪一群),我们选择为红(得罪一个)

2.找到应该插入的位置

 我们将红黑树封装为类,他有私有成员根节点_root。对于应该插入的位置,由于红黑树是一颗二叉搜索树,因此我们可以通过Key值去判断他应该存放在左子树还是右子树,具体情况二叉搜索树中有讲到,可以直接Copy一份过来。

template<class K>
class RBTree
{
	typedef RBTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		Node* cur = _root;
		Node* parent = _root;
		if (_root == nullptr)
		{
			_root = new Node(key);
			_root->_col = BLACK;
			return true;
		}
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
    }
private:
    Node* _root;
};

3.插入结点

从上面的代码可知,现在已经有了parent的位置了。但不知道应该插在parent的左还是右,这里可以通过parent的_key和我们插入结点所传递的key作比较,父亲的_key比插入的key大就插入在父亲的左边父亲的_key比插入的key小就插入在父亲的右边。

代码如下

        if (parent->_key > key)
		{
			parent->_left = new Node(key);
			cur = parent->_left;
		}
		else
		{
			parent->_right = new Node(key);
			cur = parent->_right;
		}
		cur->_parent = parent;

4.判断是否需要处理

我们所插入的新节点是红色的,如果parent为黑色,则不需要处理,parent是红色,才需要处理。(规则3:不能有连续的红节点)那么我们应该如何处理呢??先说答案(看叔叔

4.1叔叔存在且为红色

如下图,我们插入了cur结点或者cur结点是由下面的节点变色而来,首先应该将父节点p变黑(规则3,不能有连续的红色结点),那么我p变黑了,如果叔叔u的颜色为红色,那么叔叔u也得变成黑色(规则4,每条路径黑色节点相同),那么还得将祖父g变红,目的是让这条路径黑色结点个数没有变(如果不变红,黑色结点个数就从1变成了2),由于g被变红了,可能g的父亲也是红色结点,因此我们要继续往上更新

最后需要注意一点,如果祖父g是根节点,那么我们将他变成红色就违反了规则2(根节点是黑色),为防止这种情况,可以在while循环结束后,添加上"_root->_col=BLACK;"代码,保证无论如何根节点都给他置为黑色即可。

叔叔存在且为红代码如下

while (parent && parent->_col == RED)
{
	Node* grandparent = parent->_parent;

	if (grandparent->_left == parent)
	{
		//     g
		//   p   u
		// c
		Node* uncle = grandparent->_right;
		if (uncle && uncle->_col == RED)
		{
			//变颜色
			parent->_col = BLACK;
			uncle->_col = BLACK;
			grandparent->_col = RED;
			//往上更新
			cur = grandparent;
			parent = grandparent->_parent;
		}
    }
}
_root->_col=BLACK;

4.2叔叔不存在或者存在是黑色

4.2.1叔叔不存在

如果叔叔不存在,证明cur就为新增结点,并且树的结构一定如下图所示,因为无论在哪个结点左右添加上任意结点,都会破坏红黑树的性质。

这个时候,如果你只通过变色来处理,是不能够保持红黑树的性质的。因为,如果你将父亲p变黑,祖父g变红,如下图,会使得g往右边走的那条路径少一个结点,因此这种处理不可取。

  • 那我们应该如何处理呢?

需要用到之前AVL树提到的旋转,如下图,对祖结点g节点进行右旋。最后再将p变黑色,g变红色就完成了,这就保持了红黑树的性质,并且由于p是黑色结点,p的父亲无论是什么颜色,都不影响。可以选择break;防止出错,至于为什么会出错,看下面的。

  • 上图中cur为parent的左边,如果cur在parent的右边呢? 

先将p结点左旋成为上图的样子,再重复上图的步骤即可。只需要注意这样旋转后cur变成了当前树的根节点,变色应该将cur变黑,g变红,再break退出循环即可(一定要break)。

  • 为什么一定要break呢?

一是因为旋转后,已经满足红黑树的性质,不需要再次处理。

二是因为我们外层的循环条件 "parent && parent->_col == red" 已经不适用这种情况了,parent被旋转过,不在是cur的父节点

4.2.2叔叔存在且为黑 

叔叔存在且为黑,他跟上面的叔叔不存在是一样的,区别在于图是抽象图。如下图。

abcde不一定为空,对于祖父结点g来说,如果每条路径有两个黑色结点那么d和e可能有一个红色结点,或者没有结点c一定有一个黑色结点为根这个根的左子树和右子树一样要么不存在要么为红a和b为空节点。p结点为新增结点(为什么会这样,因为只有这样才不会破坏红黑树的性质)这一段分析应该不难理解(理解不了也没关系)

当然如果p是由下面a,b子树变色而来,情况就很复杂了,因此我们只画了抽象图。

对于上图这种情况依然是跟叔叔不存在一样进行旋转,对祖父g节点进行右旋,这时候p就变成了当前树的根结点,再将p变黑色,g变红色。一样尽量break。

对于cur在parent的右边,跟上面分析的叔叔不存在一样,对父节点p左旋,再对祖结点g右旋,再将g变红色,cur变黑色即可,看下图。

代码如下,旋转代码后续给出。

Node* grandparent = parent->_parent;

if (grandparent->_left == parent)
{
	//     g
	//   p   u
	// c
	Node* uncle = grandparent->_right;
	if (uncle && uncle->_col == RED)
	{
		//变颜色
		parent->_col = BLACK;
		uncle->_col = BLACK;
		grandparent->_col = RED;
		//往上更新
		cur = grandparent;
		parent = grandparent->_parent;
	}
	else
	{
		//     g
		//   p   u
		// c
		if(cur == parent->_left)
		{
			RotateR(grandparent);
			grandparent->_col = RED;
			parent->_col = BLACK;
			break;
		}
		//      g
		//   p     u
		//    c
		else
		{
			RotateL(parent);
			RotateR(grandparent);
			cur->_col = BLACK;
			grandparent->_col = RED;
			break;
		}
	}
}
4.2.3总结

刚刚我们讲解了如下grandparent->_left = parent ,父节点在祖父的左,叔叔在右的情况

对于如下grandparent->_right = parent ,父节点在祖父的右,叔叔在左的情况,跟上图刚刚好相反,代码也是类似的,就不再过多赘述了

附上代码

else  //grandparent->_right == parent
{
	//     g
	//   u   p
	//         c
	Node* uncle = grandparent->_left;
	if (uncle && uncle->_col == RED)
	{
		parent->_col = uncle->_col = BLACK;
		grandparent->_col = RED;
		cur = grandparent;
		parent = grandparent->_parent;
	}
	else
	{
		//     g
		//   u   p
		//         c
		if (parent->_right == cur)
		{
			RotateL(grandparent);
			parent->_col = BLACK;
			grandparent->_col = RED;
			break;
		}
		else
		{
			//     g
			//   u   p
			//      c
			RotateR(parent);
			RotateL(grandparent);

			cur->_col = BLACK;
			grandparent->_col = RED;
			break;
		}
	}
}

四、判断是否为红黑树

要判断是否为红黑树,我们需要根据它的性质来进行判断。

性质1:非红即黑                                  ------ 不需要判断,肯定非红即黑

性质2:根为黑                                     ------- 简单,直接if判断即可

难点在于判断他的性质3和性质4

性质3:不能有连续的红色节点

        由于二叉树有左右子树,如果当前结点颜色为红色,我们去判断他的孩子是否为红,因为有两个孩子,并且孩子可能还不存在,这样情况会比较复杂。如果我们选择判断当前结点的父亲是否为红色,这样可以节省很多时间,并且只有根节点没有父亲,其他结点都有父亲,这样遍历较好。

性质4:每条路径黑色结点的个数相同

        这个似乎乍眼一看,还挺难判断的,但是也有技巧,我们可以随便找出一条路径的黑色结点个数,记为valRef,再传递给递归函数去判断。同时传递当前黑色结点的个数,记为BlackNum,一开始为0,如果递归函数遇到一个黑结点,就将BlackNum+1,直到走到了空,证明这条路径走完了,可以将valRef和BlackNum进行相等判断,相等就return true,继续遍历,不相等就return false,会递归回来结束遍历。

 具体代码如下:

bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == RED)
		return false;
    //valRef为路径上黑色结点的个数
	int valRef = 0;
	Node* cur = _root;
	while (cur)   //一直往某条路径走,找出这条路径黑色结点的个数
	{
		if (cur->_col == BLACK)
			valRef++;
		cur = cur->_left;
	}
	int BlackNum = 0;
	return Check(_root,BlackNum,valRef);
}

bool Check(Node* root,int BlackNum,int valRef)
{
	if (root == nullptr)
	{
		if(BlackNum == valRef)
		{
			return true;
		}
		else
		{
			cout << "每条路径黑色结点个数不等" << endl;
			return false;
		}
	}
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "有连续的红色节点" << endl;
		return false;
	}
	if (root->_col == BLACK)
	{
		BlackNum++;
	}
	return Check(root->_left,BlackNum,valRef) && Check(root->_right, BlackNum, valRef);
}

五、中序遍历

还有中序递归遍历,这是我们老生常谈的东西,不对赘述了,附上代码 

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

六、总代码

 附上总代码(左旋右旋也在里面)RBTree.h

#pragma once

enum color
{
	RED,
	BLACK
};

template<class K>
struct RBTreeNode
{
	RBTreeNode<K>* _parent;
	RBTreeNode<K>* _left;
	RBTreeNode<K>* _right;
	K _key;
	color _col;
	RBTreeNode(const K& key)
		:_key(key)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(RED)
	{}
};

template<class K>
class RBTree
{
	typedef RBTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		Node* cur = _root;
		Node* parent = _root;
		if (_root == nullptr)
		{
			_root = new Node(key);
			_root->_col = BLACK;
			return true;
		}
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		if (parent->_key > key)
		{
			parent->_left = new Node(key);
			cur = parent->_left;
		}
		else
		{
			parent->_right = new Node(key);
			cur = parent->_right;
		}
		cur->_parent = parent;
		while (parent && parent->_col == RED)
		{
			Node* grandparent = parent->_parent;

			if (grandparent->_left == parent)
			{
				//     g
				//   p   u
				// c
				Node* uncle = grandparent->_right;
				if (uncle && uncle->_col == RED)
				{
					//变颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandparent->_col = RED;
					//往上更新
					cur = grandparent;
					parent = grandparent->_parent;
				}
				else
				{
					//     g
					//   p   u
					// c
					if(cur == parent->_left)
					{
						RotateR(grandparent);
						grandparent->_col = RED;
						parent->_col = BLACK;
						break;
					}
					//      g
					//   p     u
					//    c
					else
					{
						RotateL(parent);
						RotateR(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
			}
			else  //grandparent->_right == parent
			{
				//     g
				//   u   p
				//         c
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;
					cur = grandparent;
					parent = grandparent->_parent;
				}
				else
				{
					//     g
					//   u   p
					//         c
					if (parent->_right == cur)
					{
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
					else
					{
						//     g
						//   u   p
						//      c
						RotateR(parent);
						RotateL(grandparent);

						cur->_col = BLACK;
						grandparent->_col = RED;
						break;
					}
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool Check(Node* root,int BlackNum,int valRef)
	{
		if (root == nullptr)
		{
			if(BlackNum == valRef)
			{
				return true;
			}
			else
			{
				cout << "每条路径黑色结点个数不等" << endl;
				return false;
			}
		}
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续的红色节点" << endl;
			return false;
		}
		if (root->_col == BLACK)
		{
			BlackNum++;
		}
		return Check(root->_left,BlackNum,valRef) && Check(root->_right, BlackNum, valRef);
	}

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col == RED)
			return false;
		int valRef = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				valRef++;
			cur = cur->_left;
		}
		int BlackNum = 0;
		return Check(_root,BlackNum,valRef);
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		subL->_right = parent;
		Node* grandparent = parent->_parent;
		parent->_parent = subL;
		if (grandparent == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
			return;
		}
		if (grandparent->_left == parent)
		{
			grandparent->_left = subL;
		}
		else
		{
			grandparent->_right = subL;
		}
		subL->_parent = grandparent;
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		subR->_left = parent;
		Node* grandparent = parent->_parent;
		parent->_parent = subR;
		if (grandparent == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
			return;
		}
		if (grandparent->_left == parent)
		{
			grandparent->_left = subR;
		}
		else
		{
			grandparent->_right = subR;
		}
		subR->_parent = grandparent;
	}
	Node* _root = nullptr;
};

 test.cpp文件

#include<iostream>
#include<vector>
#include<ctime>
using namespace std;
#include"RBTree.h"

//int main()
//{
//	RBTree<int> rbt;
//	int arr[] = { 7, 6, 2, 1, 3, 6, 8, 9, 7, 10 };
//	for(auto e : arr)
//	{
//		rbt.Insert(e);
//	}
//	rbt.InOrder();
//	cout << rbt.IsBalance() << endl;
//	return 0;
//}

int main()
{
	const int N = 1000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}
	RBTree<int> rbt;
	for (auto e : v)
	{
		if (e == 24473)
		{
			int i = 0;
		}
		rbt.Insert(e);
		rbt.IsBalance();
	}
	rbt.InOrder();
	cout << rbt.IsBalance() << endl;
	return 0;
}

测试 

感谢大家观看!!!

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

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

相关文章

Vue3使用kkFileView预览文件pdf

kkFileView - 在线文件预览kkFileView官网 - kkFileView使用Spring Boot搭建&#xff0c;易上手和部署&#xff0c;基本支持主流办公文档的在线预览&#xff0c;如doc,docx,Excel,pdf,txt,zip,rar,图片等等https://kkfileview.keking.cn/zh-cn/docs/usage.html业务场景&#xf…

STM32 外部中断配置与中断函数设计

单片机学习 目录 文章目录 一、外部中断配置步骤 1.1配置RCC 1.2配置GPIO 1.3配置AFIO 1.4配置EXTI 1.5配置NVIC 二、中断函数设计 总结 一、外部中断配置步骤 第一步&#xff1a;配置RCC&#xff0c;把涉及外设的时钟打开。第二步&#xff1a;配置GPIO&#xff0c;选择…

【好书推荐-第30期】开发者请注意!因果推断与机器学习,终于有人能讲明白啦!

本文目录 一、因果推断二、因果推断的前世今生三、总结四、赠书条件 今天给各位读者推荐一本好书&#xff1a;《机器学习高级实践&#xff1a;计算广告、供需预测、智能营销、动态定价》&#xff0c;好书链接。 2023年初是人工智能爆发的里程碑式的重要阶段&#xff0c;以Open…

【JavaEE初阶】 博客系统项目--前端页面设计实现

文章目录 &#x1f332;主要内容&#x1f38d;预期效果&#x1f6a9;博客列表页效果&#x1f6a9;博客详情页&#x1f6a9;博客登录页&#x1f6a9;博客编辑页 &#x1f340;实现博客列表页&#x1f6a9;实现导航栏&#x1f388;页面主体部分 &#x1f384;实现博客详情页&…

Linux MeterSphere一站式开源持续测试平台远程访问

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

CNN对 MNIST 数据库中的图像进行分类

加载 MNIST 数据库 MNIST 是机器学习领域最著名的数据集之一。 它有 70,000 张手写数字图像 - 下载非常简单 - 图像尺寸为 28x28 - 灰度图 from keras.datasets import mnist# 使用 Keras 导入MNIST 数据库 (X_train, y_train), (X_test, y_test) mnist.load_data()print(&…

许战海战略文库|主品牌升级为产业技术品牌,引领企业全球化发展

在当今高速发展的全球经济中&#xff0c;企业品牌已经成为其核心资产之一。这不仅仅是因为品牌可以为消费者带来识别度&#xff0c;更重要的是&#xff0c;它们可以为企业带来深厚的竞争壁垒。但对于许多企业来说&#xff0c;特别是技术密集型企业&#xff0c;仅仅依靠主品牌的…

Maven总结

文章目录 为什么学习Maven?一、Maven项目架构管理工具二、Maven的下载安装及配置1.maven的下载2.maven目录结构3.配置阿里云镜像和本地仓库:4.maven配置环境变量。5.阿里云镜像和本地仓库说明 三、idea中maven的操作1.以模板的形式创建maven项目2.其他配置maven的方式3.不勾模…

竞赛选题 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python

文章目录 1 简介2 传统机器视觉的手势检测2.1 轮廓检测法2.2 算法结果2.3 整体代码实现2.3.1 算法流程 3 深度学习方法做手势识别3.1 经典的卷积神经网络3.2 YOLO系列3.3 SSD3.4 实现步骤3.4.1 数据集3.4.2 图像预处理3.4.3 构建卷积神经网络结构3.4.4 实验训练过程及结果 3.5 …

linux 搭建Nginx网页(编译安装)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

ABAP: JSON 报文解析——/ui2/cl_json

1、JSON数组 报文格式如下&#xff0c;是JSON 数组类型的。 [{"I_TYPE":"V","I_BUSINESSSCOPE":"1001"},{"I_TYPE":"V","I_BUSINESSSCOPE":"1002"} ] json转换为SAP内表&#xff1a; TYP…

分割回文串

题目链接 分割回文串 题目描述 注意点 s 仅由小写英文字母组成返回 s 保证每个子串都是回文串所有可能的分割方案 解答思路 从左到右将字符串进行分割&#xff0c;分割左侧部分判断是否是回文子串&#xff0c;如果不是说明不满足题意可以忽略&#xff1b;如果是则可以对右…

数字营销:概述和类型

数字营销无处不在。公司已经开始采用密集的数字营销活动来接触目标受众。从社交媒体句柄到网站&#xff0c;数字营销彻底改变了互联网时代产品和服务的营销和推广方式。本文将详细讨论数字营销的范围和类型。 什么是数字营销&#xff1f; 数字营销使用社交媒体、电子邮件、网…

逆袭之战,线下门店如何在“?”萧条的情况下实现爆发增长?

未来几年&#xff0c;商业走势将受到全球经济形势、科技进步和消费者需求变化等多种因素的影响。随着经济复苏和消费者信心提高&#xff0c;消费市场将继续保持增长&#xff0c;品质化、个性化、智能化等将成为消费趋势。同时&#xff0c;线上购物将继续保持快速增长&#xff0…

Python编程基础

Python是一种简单易学的编程语言&#xff0c;广泛应用于Web开发、数据分析、人工智能等领域。无论您是初学者还是有一定编程经验的人士&#xff0c;都可以从Python的基础知识开始建立自己的编程技能。 目录 理论Python语言的发展程序设计语言的分类静态语言与脚本语言的区别 代…

高精度基准电压源测试方法有哪些

高精度基准电压源是一种能够产生稳定、可控的电压信号的设备&#xff0c;广泛应用于科学研究、工业检测和仪器仪表校准等领域。为了保证电压信号的准确性和可靠性&#xff0c;在使用高精度基准电压源进行测试时&#xff0c;需要采取一系列的测试方法和技术手段。 校准和验证是使…

使用群晖Synology Office提升生产力:如何多人同时编辑一个文件

使用群晖Synology Office提升生产力&#xff1a;多人同时编辑一个文件 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 文章目录 使用群晖Synol…

【虚拟机Ubuntu 18.04配置网络】

虚拟机Ubuntu 18.04配置网络 1.配置网络连接方式,查看自己网关 2.修改主机名 3.修改系统配置1.配置网络连接方式,查看自己网关 选择虚拟机镜像设置网络连接模式,可以选择桥接或者NAT连接(我这里选择是NAT连接) 确定自己网关&#xff0c;可以在虚拟机 -》 编辑 -》虚拟网络编…

vue3实现element table缓存滚动条

背景 对于后台管理系统&#xff0c;数据的展示形式大多都是通过表格&#xff0c;常常会出现的一种场景&#xff0c;从表格跳到二级页面&#xff0c;再返回上一页时&#xff0c;需要缓存当前的页码和滚动条的位置&#xff0c;以为使用keep-alive就能实现这两种诉求&#xff0c;…

Uni-app智慧工地可视化信息云平台源码

智慧工地的核心是数字化&#xff0c;它通过传感器、监控设备、智能终端等技术手段&#xff0c;实现对工地各个环节的实时数据采集和传输&#xff0c;如环境温度、湿度、噪音等数据信息&#xff0c;将数据汇集到云端进行处理和分析&#xff0c;生成各种报表、图表和预警信息&…
最新文章