从2-3-4树到红黑树原理分析以及C++实现红黑树建树

news2024/7/4 5:41:43

总结规律:

     1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
          红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点---------------不要调整)

      
     2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
          这里有6种小情况(左3,右3,还有2个左中右不需要调整)------左3,右3需要调整,其余2个不需要调整

          左3,右3(叔叔结点不存在):

                

还有2个左中右不需要调整

        
          红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)

注意:叔叔结点存在且为黑 且为左3或者右3结构 也需要进行调整(后面会总结讲到)
     3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并

(叔叔结点存在且为红,需要进行调整


          红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑

>>由以上的2-3-4树分析可得出需要调整的情况如下:

(一)左三(结点插在祖父节点的左子树)
1、“叔叔存在且为红”的时候,只需要进行颜色调整+继续向上调整

2、若新增结点挂在父结点的左子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行一次“右旋”,再进行“颜色调整

3、若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“左旋”后“右旋”,再进行“颜色调整”

(二)右三(结点插在祖父节点的右子树)
1、“叔叔存在且为红”的时候,只需要进行颜色调整+继续向上调整

2、若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行一次“左旋”,再进行“颜色调整”

3、若新增结点挂在父结点的左子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“右旋”后“左旋”,再进行“颜色调整”

>>这些情况后文会详解分析并给出核心代码

 分左3,右3情况讨论:

何为调整?(颜色调整+旋转调整)

(一)左三(结点插在祖父节点的左子树)

1、“叔叔存在且为红”的时候,只需要进行颜色调整+继续向上调整,即把父亲结点和叔叔结点的颜色设置为BLACK把祖父结点的颜色设置为RED(若祖父结点为根结点,则再变黑);

讨论可能出现的问题,新增结点加入之后,经过变色调整后,还需要继续向上调整

 部分核心代码: 

//1、uncle存在且为红
if (uncle && uncle->color == RED) {
	/* 变色 + 继续向上处理 */
	//①变色
	p->color = uncle->color = BLACK;
	grandfather->color = RED;

	//②继续向上处理
	cur = grandfather;
	p = cur->parent;
}

2、若新增结点挂在父结点的左子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行一次“右旋”,再进行“颜色调整”把父结点颜色设置为BLACK,把祖父结点的颜色设置为RED

部分核心代码: 

//2、若新增结点挂在父结点的左子树(前提),uncle不存在/存在且为黑进行统一调整 
if (cur == p->left) {
	//单旋
	rightRotate(grandfather);
	p->color = BLACK;
	grandfather->color = RED;
}

3、若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“左旋”后“右旋”,再进行“颜色调整”,把新增(当前)结点颜色设置为BLACK,把祖父结点的颜色设置为RED

分析规律:其实两次“旋转”,为了让第一次旋转操作可以恢复到2,就是祖父结点、父亲结点以及新增(当前)结点都在一条直线上。所以在这种情况下,可以先“左旋”,使之恢复到2,然后再进行和2一样的“右旋”操作。

部分核心代码: 

//若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“左旋”后“右旋”,再进行“颜色调整”
leftRotate(p);
rightRotate(grandfather);
cur->color = BLACK;
grandfather->color = RED;

左三全部情况汇总如下:

(二)右三(结点插在祖父节点的右子树)

1、“叔叔存在且为红”的时候,只需要进行颜色调整+继续向上调整,即把父亲结点和叔叔结点的颜色设置为BLACK把祖父结点的颜色设置为RED(若祖父结点为根结点,则再变黑);

讨论可能出现的问题,新增结点加入之后,经过变色调整后,还需要继续向上调整

 部分核心代码:

//1、uncle存在且为红
if (uncle && uncle->color == RED) {
	/* 变色 + 继续向上处理 */
	//①变色
	p->color = uncle->color = BLACK;
	grandfather->color = RED;

	//②继续向上处理
	cur = grandfather;
	p = cur->parent;
}

2、若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行一次“左旋”,再进行“颜色调整”把父结点颜色设置为BLACK把祖父结点的颜色设置为RED

部分核心代码:

//2、若新增结点挂在父结点的右子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行一次“左旋”,再进行“颜色调整”
if (cur == p->right) {
	//单旋
	leftRotate(grandfather);
	p->color = BLACK;
	grandfather->color = RED;
}

3、若新增结点挂在父结点的左子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“右旋”后“左旋”,再进行“颜色调整”,把新增(当前)结点颜色设置为BLACK,把祖父结点的颜色设置为RED

分析规律:其实两次“旋转”,为了让第一次旋转操作可以恢复到2,就是祖父结点、父亲结点以及新增(当前)结点都在一条直线上。所以在这种情况下,可以先“右旋”,使之恢复到2,然后再进行和2一样的“左旋”操作。

部分核心代码:

//若新增结点挂在父结点的左子树(前提),出现"叔叔不存在"或者“叔叔存在且为黑”,两种情况统一操作:先进行两次“旋转”,先“右旋”后“左旋”,再进行“颜色调整”
rightRotate(p);
leftRotate(grandfather);
cur->color = BLACK;
grandfather->color = RED;

右三全部情况汇总如下:

左旋操作:

》》围绕p左旋:

        ①p是左孩子
              pf                       pf
              /                         /
             p                       pr(r)
            / \                         / \
          pl   pr(r)      ==>   p   rr(r)
               / \                   / \
              rl  rr              pl  rl

        ②p是右孩子

           pf                      pf
             \                        \
             p                       pr(r)
            / \                         / \
          pl   pr(r)      ==>   p   rr(r)
               / \                   / \
              rl  rr              pl  rl

//左旋
void leftRotate(Node* p) {
	Node* r = p->right;
	Node* rl = r->left;
	p->right = rl;
	if (rl) {
		rl->parent = p;
	}
	Node* parentParent = p->parent;
	//(双向)
	r->left = p;//p是r的左孩子
	p->parent = r;//r是p的父亲

	if (p == root) {//若p是根结点
		root = r;
		r->parent = nullptr;
	}
	else {
		if (parentParent->left == p) { //p是左孩子
			parentParent->left = r;
		}
		else { //p是右孩子
			parentParent->right = r;
		}
		r->parent = parentParent;
	}
}

 右旋操作:

右旋
        》》围绕p右旋:

        ①p是左孩子

            pf                         pf
            /                            /
           p                          pl(l)
          / \           ==》         / \
       pl(l) pr                     ll   p
        / \                            / \
       ll  lr                        lr  pr

        ②p是右孩子

           pf                          pf
            \                              \
             p                           pl(l)
            / \             ==>          / \
         pl(l) pr                      ll   p
          / \                            / \
         ll  lr                       lr  pr

//右旋
void rightRotate(Node*& p) {
	Node* l = p->left;
	Node* lr = l->right;
	p->left = lr;
	if (lr) {
		lr->parent = p;
	}
	Node* parentParent = p->parent;
	l->right = p;
	p->parent = l;

	if (p == root) {
		root = l;
		root->parent = nullptr;
	}
	else {
		if (parentParent->left == p) {
			parentParent->left = l;
		}
		else {
			parentParent->right = l;
		}
		l->parent = parentParent;
	}
}

比较结点值的大小

int compareTo(const pair<k, v> kv, Node* cur) {
		if (kv.first < cur->kv.first) 
			return -1;
		else if (kv.first > cur->kv.first) 
			return 1;
		else 
			return 0;
	}

调整红黑树

void fixAfterPut(Node* p, Node* cur) {
	while (p && p->color == RED) {
		Node* grandfather = p->parent;
		if (p == grandfather->left) {
			Node* uncle = grandfather->right;
			//1、uncle存在且为红
			if (uncle && uncle->color == RED) {
				/* 变色 + 继续向上处理 */
				//①变色
				p->color = uncle->color = BLACK;
				grandfather->color = RED;

				//②继续向上处理
				cur = grandfather;
				p = cur->parent;
			}
			else {//2、uncle不存在/存在且为黑 
				if (cur == p->left) {
					//单旋
					rightRotate(grandfather);
					p->color = BLACK;
					grandfather->color = RED;
				}
				else {
					//双旋
					leftRotate(p);
					rightRotate(grandfather);
					cur->color = BLACK;
					grandfather->color = RED;
				}
				break;
			}
		}
		else {
			Node* uncle = grandfather->left;
			//1、uncle存在且为红
			if (uncle && uncle->color == RED) {
				/* 变色 + 继续向上处理 */
				//①变色
				p->color = uncle->color = BLACK;
				grandfather->color = RED;

				//②继续向上处理
				cur = grandfather;
				p = cur->parent;
			}
			else {//2、uncle不存在/存在且为黑 
				if (cur == p->right) {
					//单旋
					leftRotate(grandfather);
					p->color = BLACK;
					grandfather->color = RED;
				}
				else {
					//双旋
					rightRotate(p);
					leftRotate(grandfather);
					cur->color = BLACK;
					grandfather->color = RED;
				}
				break;
			}
		}
	}
}

获取红黑树的高度

int Height()
{
	return _Height(root);
}
int _Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int leftHeight = _Height(root->left);
	int rightHeight = _Height(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

完整代码:

/*
	1、2-3-4树:新增元素+2节点合并(节点中只有1个元素)=3节点(节点中有2个元素)
		红黑树:新增一个红色节点+黑色父亲节点=上黑下红(2节点)--------------不要调整
	2、2-3-4树:新增元素+3节点合并(节点中有2个元素)=4节点(节点中有3个元素)
		这里有6种情况(左3,右3,还有2个左中右不需要调整)----------------左3,右3需要调整,其余2个不需要调整
		红黑树:新增红色节点+上黑下红=排序后中间节点是黑色,两边节点都是红色(3节点)
	3、2-3-4树:新增一个元素+4节点合并=原来的4节点分裂,中间元素升级为父节点,新增元素与剩下的其中一个合并
		红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色=爷爷节点变红,父亲和叔叔变黑,如果爷爷是根节点,则再变黑

*/
#include<iostream>
using namespace std;
#include<vector>
#include<cmath>
#include<string>
#include<vector>
#define N 100
//#include "StringBuilder.h"

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 color;
	RBTreeNode(const pair<k, v>& kv)
		:left(nullptr)
		, right(nullptr)
		, parent(nullptr)
		, kv(kv)
		, color(RED)
	{}
};

template<class k, class v>
class RBTree {
	typedef RBTreeNode<k, v> Node;
public:
	
	RBTree()
		:root(nullptr) 
	{}
	//插入结点
	bool Insert(const pair<k, v>& kv) {
		if (root == nullptr) {
			root = new Node(kv);
			root->color = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = root;
		int res;
		while (cur) {
			res = this->compareTo(kv, cur);
			if (res == 1) {
				parent = cur;
				cur = cur->right;
			}
			else if (res == -1) {
				parent = cur;
				cur = cur->left;
			}
			else {
				return false;
			}
		}
		cur = new Node(kv);
		cur->color = RED;//新增节点
		res = this->compareTo(kv, parent);
		//如果比较最终落在右子树,则直接将父节点右指针指向cur
		if (res == 1) {
			parent->right = cur;
			cur->parent = parent;
		}
		//如果比较最终落在左子树,则直接将父节点左指针指向cur
		else {
			parent->left = cur;
			cur->parent = parent;
		}
		//调整
		fixAfterPut(parent,cur);
		root->color = BLACK;
		return true;
	}

	void fixAfterPut(Node* p,Node* cur) {
		while (p && p->color == RED) {
			Node* grandfather = p->parent;
			if (p == grandfather->left) {
				Node* uncle = grandfather->right;
				//1、uncle存在且为红
				if (uncle && uncle->color == RED) {
					/* 变色 + 继续向上处理 */
					//①变色
					p->color = uncle->color = BLACK;
					grandfather->color = RED;
					
					//②继续向上处理
					cur = grandfather;
					p = cur->parent;
				}
				else {//2、uncle不存在/存在且为黑 
					if (cur == p->left) {
						//单旋
						rightRotate(grandfather);
						p->color = BLACK;
						grandfather->color = RED;
					}
					else {
						//双旋
						leftRotate(p);
						rightRotate(grandfather);
						cur->color = BLACK;
						grandfather->color = RED;
					}
					break;
				}
			}
			else {
				Node* uncle = grandfather->left;
				//1、uncle存在且为红
				if (uncle && uncle->color == RED) {
					/* 变色 + 继续向上处理 */
					//①变色
					p->color = uncle->color = BLACK;
					grandfather->color = RED;

					//②继续向上处理
					cur = grandfather;
					p = cur->parent;
				}
				else {//2、uncle不存在/存在且为黑 
					if (cur == p->right) {
						//单旋
						leftRotate(grandfather);
						p->color = BLACK;
						grandfather->color = RED;
					}
					else {
						//双旋
						rightRotate(p);
						leftRotate(grandfather);
						cur->color = BLACK;
						grandfather->color = RED;
					} 
					break;
				}
			}
		}
	}

	int compareTo(const pair<k, v> kv, Node* cur) {
		if (kv.first < cur->kv.first) 
			return -1;
		else if (kv.first > cur->kv.first) 
			return 1;
		else 
			return 0;
	}


	//左旋
	/*
		》》围绕p左旋:

		①p是左孩子
			  pf                       pf
			  /                        /
			 p                       pr(r)
			/ \						 / \
		  pl   pr(r)      ==>       p   rr(r)
			   / \                 / \
			  rl  rr			  pl  rl

		②p是右孩子

			pf                       pf
			 \                        \
			 p                       pr(r)
			/ \						 / \
		  pl   pr(r)      ==>       p   rr(r)
			   / \                 / \
			  rl  rr			  pl  rl

	*/
	void leftRotate(Node* p) {
		Node* r = p->right;
		Node* rl = r->left;
		p->right = rl;
		if (rl) {
			rl->parent = p;
		}
		Node* parentParent = p->parent;
		//(双向)
		r->left = p;//p是r的左孩子
		p->parent = r;//r是p的父亲

		if (p == root) {//若p是根结点
			root = r;
			r->parent = nullptr;
		}
		else {
			if (parentParent->left == p) { //p是左孩子
				parentParent->left = r;
			}
			else { //p是右孩子
				parentParent->right = r;
			}
			r->parent = parentParent;
		}
	}

	//右旋
	/*
		》》围绕p右旋:

		①p是左孩子

			pf                         pf
			/                          /
		   p						  pl(l)
		  / \           ==》         / \
	   pl(l) pr                     ll   p
		/ \                             / \
	   ll  lr                          lr  pr

		②p是右孩子

		   pf                       pf
			\                        \
			 p                       pl(l)
			/ \			 ==>		  / \
		 pl(l) pr					 ll  p
		  / \                           / \
		 ll  lr			               lr  pr

	*/
	void rightRotate(Node*& p) {
		Node* l = p->left;
		Node* lr = l->right;
		p->left = lr;
		if (lr) {
			lr->parent = p;
		}
		Node* parentParent = p->parent;
		l->right = p;
		p->parent = l;

		if (p == root) {
			root = l;
			root->parent = nullptr;
		}
		else {
			if (parentParent->left == p) {
				parentParent->left = l;
			}
			else {
				parentParent->right = l;
			}
			l->parent = parentParent;
		}
	}

	void InOrder()
	{
		_InOrder(root);
	}

	//中序遍历
	void _InOrder(Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->left);
		cout << root->kv.first << ":" << root->kv.second << endl;
		_InOrder(root->right);
	}

	/*
		检测是否满足红黑树的性质:任意选取一条路径找出黑色结点的个数作为基准值,然后依次与其他
		路径比较,如果都相同则满足红黑树的性质。
	*/
	bool IsBalance() {
		if (root && root->color == RED) {
			cout << "根结点不是黑色" << endl;//为了测试
			return false;
		}
		//最左路径黑色结点数量做基准值
		int banchmark = 0;//保存最左侧路径中黑色结点的个数
		Node* left = root;
		while (left) {
			if (left->color == BLACK)
				++banchmark;
			left = left->left;
		}
		int blackNum = 0;//递归遍历时记录黑色结点的个数
		return _IsBalance(root, banchmark, blackNum);
	}

	bool _IsBalance(Node* root, int banchmark, int blackNum)
	{
		//走到null之后,判断banchmark和blackNum是否相等
		if (root == nullptr)
		{
			if (banchmark != blackNum)
			{
				cout << "存在路径黑色结点的数量不相等" << endl;//为了测试
				return false;
			}
			return true;
		}
		if (root->color == RED && root->parent->color == RED)
		{
			cout << "出现了连续的红色结点" << endl;//为了测试
			return false;
		}
		if (root->color == BLACK)// 统计黑色节点的个数
		{
			++blackNum;
		}
		return _IsBalance(root->left, banchmark, blackNum)
			&& _IsBalance(root->right, banchmark, blackNum);
	}
	int Height()
	{
		return _Height(root);
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = _Height(root->left);
		int rightHeight = _Height(root->right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	Node* getRoot() {
		return this->root;
	}

private:
	Node* root;
};

template<class k, class v>
class TreeOperation {
	typedef RBTreeNode<k, v> Node;
	/*
	树的结构示例:
			  1
			/   \
		  2       3
		 / \     / \
		4   5   6   7
	*/
	// 用于获得树的层数
public:
	int getTreeDepth(Node* root) {
		if (root == nullptr)
			return 0;
		else
		{
			int ldep = getTreeDepth(root->left);
			int rdep = getTreeDepth(root->right);
			return (ldep < rdep) ? rdep + 1 : ldep + 1;
		}
	}
	void writeArray(Node* currNode, int rowIndex, int columnIndex, string** res, int treeDepth) {
		// 保证输入的树不为空
		if (currNode == nullptr) return;
		// 0、默认无色
		//res[rowIndex][columnIndex] = String.valueOf(currNode.getValue());
		//1、颜色表示
		if (currNode->color == BLACK) {//黑色,加色后错位比较明显
			res[rowIndex][columnIndex] = ("\033[40;3m" + currNode->kv.second + "\033[0m");
			//cout << "-----------------------------------------" << res[rowIndex][columnIndex] << endl;
		}
		else {
			res[rowIndex][columnIndex] = ("\033[31;3m" + currNode->kv.second + "\033[0m");
			//cout << "=========================================" << res[rowIndex][columnIndex] << endl;
		}
		//2、R,B表示
		//res[rowIndex][columnIndex] = String.valueOf(currNode.getValue()+"-"+(currNode.isColor()?"B":"R")+"");

		// 计算当前位于树的第几层
		int currLevel = ((rowIndex + 1) / 2);
		// 若到了最后一层,则返回
		if (currLevel == treeDepth) return;
		// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
		int gap = treeDepth - currLevel - 1;
		// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
		if (currNode->left != nullptr) {
			res[rowIndex + 1][columnIndex - gap] = "/";
			writeArray(currNode->left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
		}

		// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
		if (currNode->right != nullptr) {
			res[rowIndex + 1][columnIndex + gap] = "\\";
			writeArray(currNode->right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
		}
	}
	void show(Node* root) {
		if (root == nullptr) printf("EMPTY!");
		//printf("==============");
		// 得到树的深度
		int treeDepth = getTreeDepth(root);

		// 最后一行的宽度为2的(n - 1)次方乘3,再加1
		// 作为整个二维数组的宽度
		int arrayHeight = treeDepth * 2 - 1;
		int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
		/*printf("==============%d \n", arrayHeight);
		printf("==============%d \n", arrayWidth);*/

		// 用一个字符串数组来存储每个位置应显示的元素
		//string res[arrayHeight][arrayWidth];

		int rows = arrayHeight;
		int cols = arrayWidth;

		//动态创建二维字符数组arr[m][n]
		string** res;
		res = new string * [arrayHeight]; //创建行指针
		for (int i = 0; i < arrayHeight; i++)
			res[i] = new string[arrayWidth]; //为每行分配空间

		// 对数组进行初始化,默认为一个空格
		for (int i = 0; i < arrayHeight; i++)
		{
			for (int j = 0; j < arrayWidth; j++)
			{
				res[i][j] = " ";
			}
		}

		// 从根节点开始,递归处理整个树
		writeArray(root, 0, arrayWidth / 2, res, treeDepth);
		
		for (int i = 0; i < arrayHeight; i++)
		{
			vector<string> strVector = {};
			for (int j = 0; j < arrayWidth; j++) {
				cout<< res[i][j];
				int tempStrLen = res[i][j].length();
				if (tempStrLen > 1 && j <= arrayHeight - 1) {
					j += tempStrLen > 4 ? 2 : tempStrLen - 1;
				}
				//strVector.push_back(res[i][j]);
				/*if (res[i][j].length() > 1 && j <= arrayHeight - 1) {
					j += res[i][j].length() > 4 ? 2 : res[i][j].length() - 1;
				}*/
			}
			cout << endl;
			/*string str="";
			for (vector<string>::const_iterator it = strVector.begin(); it != strVector.end(); it++)
			{
				str.append(*it);
			}
			cout << str << endl;*/
		}
	}
};



void insertOpt() {
	RBTree<string, string> t;
	int inputV = 0;
	string str = "";
	while (true) {
		printf("请输入你要插入的节点:");
		scanf_s("%d", &inputV);
		str = to_string(inputV);
		
		//这里代码最多支持3位数,3位以上的话红黑树显示太错位了,这里就不重构代码了,大家可自行重构
		
		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		t.Insert(make_pair(str, str));
		TreeOperation<string, string> Tp;
		Tp.show(t.getRoot());
	}
}


void TestRBTree()
{
	//RBTree<int, int> t;
	RBTree<string, string> t;
	//string a[] = { "5","4","3","2","1","0"};
	//string a[] = { "16","3","7","11","9","26","18","14","15"};
	 //string a[] = { "4","2","6","1","3","5","15","7","16","14"};
	 //string a[] = { "4","2","6","1","3","5","15","7","16","14","8","9","10","11","12","13","14"};
	string a[] = { "6","4","10","8","3","7","9"};
	for (auto str : a)
	{
		if (str.length() == 1) {
			str = "00" + str;
		}
		else if (str.length() == 2) {
			str = "0" + str;
		}
		t.Insert(make_pair(str, str));
		//cout << "Insert" << e << ":" << t.IsBalance() << endl;
	}
	/*t.InOrder();
	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;*/
	//TreeOperation<int, int> Tp;
	TreeOperation<string, string> Tp;
	Tp.show(t.getRoot());
	
}

int main() {
	/*TestRBTree();
	cout << endl;
	cout << endl;
	cout << endl;*/
	insertOpt();
	system("pause");
	return 0;
}

 效果展示如下:

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

上班族如何安排时间提高工作效率?

对于上班族来说&#xff0c;合理安排时间可以兼顾生活和工作&#xff0c;不仅能够减少加班次数&#xff0c;还可以提高工作效率&#xff0c;减少工作中的负面情绪。但是有不少小伙伴表示&#xff0c;自己不知道如何安排时间从而提高工作效率&#xff0c;这应该怎么办呢&#xf…

张勇:阿里云是一家云计算产品公司,要坚定走向“产品被集成”

4月26日&#xff0c;在2023阿里云合作伙伴大会上&#xff0c;阿里巴巴董事会主席兼CEO、阿里云智能CEO张勇表示&#xff0c;阿里云的核心定位是一家云计算产品公司&#xff0c;生态是阿里云的根基。让被集成说到做到的核心&#xff0c;是要坚定走向“产品被集成”。 张勇表示&a…

小米13 Ultra:携光前行,追求每一束光的精确还原

“光&#xff0c;是影像的原点”&#xff0c;一切色彩、影调都在于光。我们目之所及的大千世界&#xff0c;皆被光与影一笔一划细细勾勒&#xff0c;为“视”界晕染上或鲜明、或复古、或反差、或梦幻的色调。我们用“光”去描绘、定义“影像”&#xff0c;让一切平凡的事物&…

Notion AI 胜于 ChatGPT ?

去年&#xff08;2022年&#xff09;12 月初&#xff0c;在社区中 OpenAI 的 ChatGPT 刚出来就火了一把&#xff0c;当时一度因为访问量太大导致崩溃宕机&#xff1b;最近&#xff08;2023 年1 月底&#xff09; ChatGPT 又火了&#xff0c;资本市场新增 ChatGPT 概念&#xff…

入局生成式AI,看好亚马逊(AMZN)中期表现

来源; 猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;由于近期亚马逊&#xff08;AMZN&#xff09;宣布发布多项生成式AI以及AIGC相关产品&#xff0c;入局全球大模型竞赛当中。中信证券发布研报看好入局生成式AI。中信证券在研报中称&#xff0c;亚马逊作为北美…

【Git】拉取代码/提交代码

1.从将本地代码放入远程仓库 (如果有分支的情况) [git checkout xx切换分支后 git add . 将本地所有改动文件新增 commit之后 git push(将代码全部提交)] 分支操作 #查看分支 git branch #创建分支 git branch test #切换分支 git checkout test #修改代码 #提交代码git ad…

DPDK和RDMA的区别

网络的发展好像在各方面都是滞后于计算和存储&#xff0c;时延方面也不例外&#xff0c;网络传输时延高&#xff0c;逐渐成为了数据中心高性能的瓶颈。因为传统两个节点间传输数据的网络路径上有大量的内存拷贝&#xff0c;导致网络传输效率低下&#xff0c;网络数据包的收发处…

MySQL——索引

目录 一、索引 1.1 索引的概念 1.2 索引的运用 1.2.1 索引的创建 1.2.2 查看表的索引 ​1.2.3 创建索引 1.2.4 删除索引 1.2.5 总结 二、索引底层的数据结构 B 树的特点 一、索引 1.1 索引的概念 当我们是使用查询语句对表中的数据进行条件查询的时候&#xff0c;M…

Python小姿势 - Python爬取数据的库——Scrapy

Python爬取数据的库——Scrapy 一、爬虫的基本原理 爬虫的基本原理就是模拟人的行为&#xff0c;使用指定的工具和方法访问网站&#xff0c;然后把网站上的内容抓取到本地来。 爬虫的基本步骤&#xff1a; 1、获取URL地址&#xff1a; 2、发送请求获取网页源码&#xff1b; 3、…

NAT网络地址转换

1.前言 随着网络设备的数量不断增长&#xff0c;对IPv4地址的需求也不断增加&#xff0c;导致可用IPv4地址空间逐渐耗尽。解决IPv4地址枯竭问题的权宜之计是分配可重复使用的各类私网地址段给企业内部或家庭使用。但是&#xff0c;私有地址不能在公网中路由&#xff0c;即私网…

数据结构,Map和Set的使用方法

在数据结构中我们经常会使用到 Map 和 Set &#xff0c;Map 和 Set 到底是什么&#xff0c;它怎样去使用呢&#xff1f;因此博主整理出 Map 和 Set 这两个接口的介绍与使用方法。 目录 1. 啥是Map和Set? 1.1 Map和Set的模型 2. Map的使用 2.1Map的说明 2.2 Java中Map常用…

【C++】列表初始化声明范围forSTL容器新变化

文章目录 什么是C11列表初始化**C98中{}的初始化**内置类型的列表初始化 关于initializer_list使用场景: 声明auto-变量类型推导decltype类型推导nullptr 范围forSTL的新变化新容器:容器中的一些新方法 什么是C11 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1),使得C…

Java 输出机制 数据类型

目录 一、输出机制 1.print和println的差别 2.可接收不同类型参数 3.输出函数中 符号的使用 二、Java 数据类型 1.整型类型 2.浮点类型 3.字符类型 三、基本数据类型转换 1.自动类型转换 2.强制类型转换 3.练习题 四、基本数据类型和String类型的转换 1.基本类…

【LeetCode】 309.最佳买卖股票时机含冷冻期

309.最佳买卖股票时机含冷冻期&#xff08;中等&#xff09; 思路 状态定义 一、很容易想到四种状态&#xff1a; a.今天买入&#xff1b;b.今天卖出&#xff1b;c.昨天卖出&#xff0c;今天处于冷冻期&#xff0c;无法进行操作&#xff1b;d.今天不操作&#xff0c;处于持有…

SD卡变成RAW格式怎么办?SD卡RAW格式的解决办法

使用SD卡的小伙伴有没有遇到这种情况&#xff0c;SD卡无法访问提示格式化&#xff0c;查看SD卡的属性发现文件系统类型变成RAW格式&#xff0c;而非之前的NTFS或FAT32格式。那么当SD卡变成raw格式怎么办&#xff1f;如果里面有重要数据怎么办&#xff1f;SD卡RAW格式怎么恢复数…

【Java】什么是SOA架构?与微服务有什么关系?

文章目录 服务化架构微服务架构 我的一个微服务项目&#xff0c;有兴趣可以一起做 服务化架构 我们知道&#xff0c;早期的项目&#xff0c;我们都是把前后端的代码放在同一个项目中&#xff0c;然后直接打包运行这个项目&#xff0c;这种项目我们称之为单体项目&#xff0c;比…

m4a怎么转换成mp3的4种方法值得收藏

m4a怎么转换成mp3&#xff1f;首先我们得了解m4a是什么格式。m4a是MPEG-4音频标准的文件扩展名&#xff0c;它是一种音频格式&#xff0c;由苹果公司推出。该格式的音质没有损失&#xff0c;且不受版权保护&#xff0c;因此可以进行自由编辑和转发。该格式的兼容性相对较弱&…

PIE-SAR软件自动化编译与发布

1.背景 SVN版本控制下多人协调编写代码&#xff0c;会经常性的提交新功能&#xff0c;修改完善已有功能。产品经理、测试人员需定期回归测试&#xff0c;确保禅道Bug已经修复&#xff0c;这就需要经常性地打包软件。为了节省编译时间&#xff0c;也方便产品经理可随时去取最新…

MiniGPT-4,开源了!

上个月GPT-4发布时&#xff0c;我曾写过一篇文章分享过有关GPT-4的几个关键信息。 当时的分享就提到了GPT-4的一个重要特性&#xff0c;那就是多模态能力。 比如发布会上演示的&#xff0c;输入一幅图&#xff08;手套掉下去会怎么样&#xff1f;&#xff09;。 GPT-4可以理解…

基于51单片机的差分双路电压检测(基于ADC0832)

文章目录 前言一、本文任务二、材料三、电路图四、代码解读1.引脚定义及参数2.定时器中断与延时开启3.数码管显示定义及ADC0832函数初始化4.数据转换及数码管显示5.主函数及定时器函数6.所有代码&#xff08;就一个c文件&#xff09; 总结 前言 博主终于又空出时间啦&#xff…