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

[ 数据结构 - C++]红黑树RBTree

在上篇文章我们了解了第一种平衡二叉搜索树AVL树,我们知道AVL树是通过平衡因子来控制左右子树高度差,从而将二叉树变成一颗平衡二叉搜索树。本篇文章我们将要了解另外一种平衡规则控制的二叉搜索树--红黑树(RBTree)

目录

1.红黑树的概念

2.红黑树的性质

3.红黑树节点的定义

4.红黑树的插入Insert

代码实现:

5.红黑树的验证

6.红黑树与AVL树的比较

7.红黑树测试

附录:


1.红黑树的概念

红黑树是一种二叉搜索树,在二叉树的每个节点上增加一个存储为表示该节点的颜色。颜色可以红色(RED)或黑色(BLACK)。通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长2倍,因而是接近平衡的。(例如:下图正是一颗红黑树)

2.红黑树的性质

根据上图示例,我们能够发现一颗红黑树具有如下几条性质:

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

满足以上红黑树的性质,就能保证该棵树是一颗平衡树吗?为什么?

我们在概念中提到,红黑树确保没有一条路径会比其他路径长2倍,因而是接近平衡的。因此问题就转变为为什么如果这颗树是一颗红黑树,就能保证其最长路径中节点个数不会超过最短路径节点个数的两倍?

  • 根据第三点,红色节点的孩子是黑色的,这点能保证没有连续的两个红色节点。
  • 根据第四点,从该节点到后代的叶子路径上,包含数量相同的黑色节点,此处假如是从根节点计算,最坏情况下根节点左孩子为空,此时说明了从根节点开始一条路径上只有2个黑色节点(不包含NULL节点),这也要求了根节点的右孩子必须且有2个黑色节点,此时根节点的右孩子可以存在一个红色节点,但是配合第三点,如果存在红色节点且红色节点的孩子一定是黑色的,此时右子树中任意一条路径的黑色节点已经达到两个,说明不能再存在黑色节点了,因此在黑色几点下最后链接一个红色节点右子树就需要停止。(如下图所示为例)

此时我们发现最短的路径长度为2(15->9),最长的路径长度为4(15->19->17->16)【其一】,这也就验证了一颗红黑树中,其最长路径中的节点个数不会超过最短路径中的节点个数的两倍!

3.红黑树节点的定义

我们依然才从KV模型来建立节点。较AVL树而言,红黑树多了一个Colour而少了一个_bf,但是结构大体相同。

enum Colour
{
	RED,
	BLACK,
};

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

	Colour _col;//节点的颜色

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

};

在上述红黑树节点的定义中,新节点的_col默认给成红色是有意为之还是红黑色都可以呢?

当我们插入一个节点之后,可能会破坏红黑树的规则,因此我们插入一个节点要尽可能的少破坏红黑树的规则。而插入红色节点是,可能会破坏性质3,因为可能会导致连续的红色节点;而插入黑色节点是,会一定破坏性质4,因为插入一个黑色节点必然会使插入节点的这一条路径黑色几点个数增加1,从而导致性质4被破坏,因此从破坏力度来说选择红色节点,插入红色节点可能破坏性质3,插入黑色节点一定破坏性质4。

从维护红黑树的角度来说,也是破坏4更难维护,因为它牵扯到每一条路径,可能会让红黑树产生翻天覆地的变化(具体详情往下看Insert)。

4.红黑树的插入Insert

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可以分为两步:

1.按照儿茶搜索树的规则插入新节点

2.检测新节点插入后,红黑树的性质是否被破坏。

bool Insert(const pair<K, V>& kv)
{
	//1.搜索树的规则插入
	//2.看是否违反平衡规则,如果违反就需要处理:旋转
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;//性质2:根节点为黑色
		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->_col = RED;//新增节点都是红色
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;
    //维护处理
	//......
}

当走完上述代码后,我们知道此时新节点已经插入到正确的位置了,现在要做的是检查和维护这颗红黑树。

  1. 如果双亲节点的颜色是黑色,那说明没有违反红黑树的任何性质,则不需要调整。
  2. 当新插入节点的双亲节点颜色红色时,此时违反性质三(不能有连续的红色节点),此时需要对红黑树进行调整。调整需分情况来讨论:(约定cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)
    1. 情况一:cur为红,p为红,g为黑,u存在且为红

注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树。

如果g是根节点,调整完成后,需要将g改为黑色

如果g是子树,g一定有双亲,且g的双亲如果是红色,需要继续向上调整

cur和p均为红,违反了性质3。解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

    1. 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑

说明:u的情况有两种:

  • 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同
  • 如果u节点存在,则其一定为黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色

p为g的左孩子,cur为p的左孩子则进行右单旋;

p为g的右孩子,cur为p的右孩子,则进行左单旋;

p,g变色--p变成黑,g变成红。(旋转规则在AVL树中有详细讲解,大家可参考上篇博文)

    1. 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

p为g的左孩子,cur为p的右孩子,则针对p做左单旋;

p为g的右孩子,cur为p的左孩子,则针对p做右单旋;

则转换为了情况二。

代码实现:

bool Insert(const pair<K, V>& kv)
	{
		//1.搜索树的规则插入
		//2.看是否违反平衡规则,如果违反就需要处理:旋转
		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);
		cur->_col = RED;//新增节点都是红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//存在连续的红色节点
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况1:
				if (uncle && uncle->_col == RED)//叔叔存在且为红
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//双旋
					{
						//    g
						//  p
						//    c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//情况一:
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						// g
						//   p
						//     c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else //双旋
					{
						// g
						//   p
						// c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
		}

		//处理根 一定是黑色
		_root->_col = BLACK;
		return true;
	}
	//左旋
	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 (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				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;
		}
	}

5.红黑树的验证

红黑树的验证检测分为两步:

1.检测其是否满足二叉搜索树(中序遍历是否有序)

2.检测其是否满足红黑树的性质

    //检测第一步:是否为二叉搜索树
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	//检测第二步:是否满足性质
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四,每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		//统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;

		//监测当前节点与其双亲是否都为红色
		if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
		{
			cout << "违反性质三:存在连在一起的红色节点" << endl;
			return false;
		}

		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}

6.红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2 N),红黑树不追求绝对平衡,其只需要保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树的实现比较简单,所以实际运用中红黑树更多。

7.红黑树测试

红黑树测试时,我们需要知道其最长路径和其最短路径。

    int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);

		return lh > rh ? lh + 1 : rh + 1;
	}

	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);

		return lh < rh ? lh + 1 : rh + 1;
	}

我们可以生成随机数和生成有序数来加以验证

void TestRBTree2()
{
	const size_t N = 1024*1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());
		v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	cout << endl;
	cout << endl;
	cout << "是否平衡?" << t.IsBalanceTree() << endl;
	t.Height();

	//t.InOrder();
}

首先我们使用有序数来验证,我们将使用N=1024*1024个数据进行测试

接下来我们使用随机数来验证,仍然使用N=1024*1024个数据进行测试

我们发现,我们自构的红黑树均完成了以上测试。

附录:

#pragma once
#include <assert.h>
#include <vector>
#include <queue>
#include <iostream>
#include <time.h>
using namespace std;

enum Colour
{
	RED,
	BLACK,
};

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

	Colour _col;

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

};

template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
public:

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	void Height()
	{
		cout << "最长路径:" << _maxHeight(_root) << endl;
		cout << "最短路径:" << _minHeight(_root) << endl;
	}

	bool IsBalanceTree()
	{
		//检查红黑树有几条规则
		Node* pRoot = _root;
		//空树也是红黑树
		if (nullptr == pRoot)
			return true;

		//检查根节点是否满足情况
		if (BLACK != pRoot->_col)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}

		//获取任意一条路径中黑色节点的个数 --作为基准值进行比较
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;

			pCur = pCur->_left;
		}

		//监测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}


public:
	bool Insert(const pair<K, V>& kv)
	{
		//1.搜索树的规则插入
		//2.看是否违反平衡规则,如果违反就需要处理:旋转
		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);
		cur->_col = RED;//新增节点都是红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//存在连续的红色节点
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况1:
				if (uncle && uncle->_col == RED)//叔叔存在且为红
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else//叔叔不存在 或者 叔叔存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//双旋
					{
						//    g
						//  p
						//    c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//情况一:
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						// g
						//   p
						//     c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else //双旋
					{
						// g
						//   p
						// c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
		}

		//处理根 一定是黑色
		_root->_col = BLACK;
		return true;
	}

	vector<vector<int>> levelOrder() {
		vector<vector<int>> vv;
		if (_root == nullptr)
			return vv;

		queue<Node*> q;
		int levelSize = 1;
		q.push(_root);

		while (!q.empty())
		{
			// levelSize控制一层一层出
			vector<int> levelV;
			while (levelSize--)
			{
				Node* front = q.front();
				q.pop();
				levelV.push_back(front->_kv.first);
				if (front->_left)
					q.push(front->_left);

				if (front->_right)
					q.push(front->_right);
			}
			vv.push_back(levelV);
			for (auto e : levelV)
			{
				cout << e << " ";
			}
			cout << endl;

			// 上一层出完,下一层就都进队列
			levelSize = q.size();
		}

		return vv;
	}

	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 (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				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;
		}
	}
	int _maxHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _maxHeight(root->_left);
		int rh = _maxHeight(root->_right);

		return lh > rh ? lh + 1 : rh + 1;
	}

	int _minHeight(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = _minHeight(root->_left);
		int rh = _minHeight(root->_right);

		return lh < rh ? lh + 1 : rh + 1;
	}

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

	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四,每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}

		//统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;

		//监测当前节点与其双亲是否都为红色
		if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
		{
			cout << "违反性质三:存在连在一起的红色节点" << endl;
			return false;
		}

		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}

private:
	Node* _root = nullptr;
};

void TestRBTree1()
{
	//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.levelOrder();
	t.InOrder();
	t.Height();
}

void TestRBTree2()
{
	const size_t N = 1024*1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		v.push_back(rand());
		//v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	cout << endl;
	cout << endl;
	cout << "是否平衡?" << t.IsBalanceTree() << endl;
	t.Height();

	//t.InOrder();
}

相关文章:

  • 【闲笔杂谈】ArrayList的构造与扩容机制
  • Flink系列之:基于Flink CDC2.0实现海量数据的实时同步和转换
  • [架构之路-21]:目标系统 - 软件系统 - 计算机系统架构、计算机指令系统、结构化程序与分层编程。
  • acwing算法基础模版分析
  • 数据库-MySQL-基础(7)函数
  • MySQL入门学习笔记(下)
  • 15.4 - 分类树法
  • python容器
  • xilinx FPGA FX2 usb通信模块之上位机发送的数据格式
  • 阿里云对象存储OSS存储照片
  • AES、RSA、DH加密解密
  • 高效的操作符使用剖析
  • CVE-2017-12615 Tomcat任意文件上传漏洞详解
  • 10.2国庆作业(PWM实验)
  • Java开发环境基础配置
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • Python学习之路16-使用API
  • SpiderData 2019年2月13日 DApp数据排行榜
  • Spring Boot快速入门(一):Hello Spring Boot
  • 回顾 Swift 多平台移植进度 #2
  • 面试总结JavaScript篇
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ionic异常记录
  • PostgreSQL之连接数修改
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • .NET delegate 委托 、 Event 事件
  • .NET 表达式计算:Expression Evaluator
  • .net 流——流的类型体系简单介绍
  • .NetCore部署微服务(二)
  • .NET导入Excel数据
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • .net中调用windows performance记录性能信息
  • @WebServiceClient注解,wsdlLocation 可配置
  • []新浪博客如何插入代码(其他博客应该也可以)
  • [20171101]rman to destination.txt
  • [2023年]-hadoop面试真题(一)
  • [Eclipse] 详细设置护眼背景色和字体颜色并导出
  • [Editor]Unity Editor类常用方法
  • [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated c
  • [ffmpeg] aac 音频编码
  • [Flexbox] Using order to rearrange flexbox children
  • [Flutter] extends、implements、mixin和 abstract、extension的使用介绍说明
  • [HDU3710]Battle over Cities
  • [iOS开发]事件处理与响应者链
  • [json]定义、读写
  • [nowCoder] 两个不等长数组求第K大数
  • [P3097] [USACO13DEC] [BZOJ4094] 最优挤奶Optimal Milking 解题报告(线段树+DP)
  • [Spring Cloud] Nacos 实战 + Aws云服务器
  • [vue-router]vue-router 路由传参问题
  • [Windows][Linux]字体相关