【树】 二叉树 堆与堆排序 平衡(AVL)树 红黑(RB)树

news2024/9/30 15:32:50

目录

  • 1 树
    • 1.1 认识树
    • 1.2 树的相关概念
    • 1.3 树的表示
      • 孩子兄弟表示法
  • 2 二叉树
    • 2.1 概念
    • 2. 2 特殊二叉树
    • 2.3 二叉树的性质
    • 2.4 二叉树的存储结构
  • 3 堆 — 完全二叉树的顺序结构实现
    • 3.1 堆的概念
    • 3.2 核心代码
    • 3.3 堆应用
      • 1 堆排序
      • 2 TOP-K问题
  • 4 二叉树的链式存储
    • 4.1 二叉链结构与初始化
    • 4.2 核心代码
  • 5 二叉搜索树
    • 5.1 概念
    • 5.2 结构与代码实现
    • 5.3 复杂度与缺陷
  • 6 AVL树
    • 6.1 概念
    • 6.2 旋转核心代码与思想
    • 6.3 插入
    • 6.4 验证平衡
  • 7 红黑树
    • 7.1 红黑树的概念
    • 7.2 红黑树的性质
    • 7.3 插入
      • 插入思想
      • 核心代码
    • 7.4 验证红黑树

1 树

1.1 认识树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。

image-20230729150321816

  • 有一个特殊的结点,称为根结点,根节点没有前驱结点
  • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
  • 因此,树是递归定义的。
  • 注意:树形结构中
    • 子树之间不能有交集,否则就不是树形结构
    • 除了根节点外,每个结点有且仅有一个父结点
    • 一颗N各结点的树有N-1条边

1.2 树的相关概念

image-20230729150945783

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
  • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

1.3 树的表示

孩子兄弟表示法

image-20230729151544083

typedef int DataType;
struct TreeNode {
	struct TreeNode* _firstChild1;  //第一个孩子结点
	struct Node* _pNextBrother;    //指向其下一个兄弟结点
	DataType _data;   //结点中的数据域
};

2 二叉树

2.1 概念

image-20230729154946894

一棵二叉树是结点的一个有限集合,该集合:

  • 或者为空
  • 或者,由一个根节点加上两颗别称为左子树和右子树的二叉树组成

注意:

  • 二叉树不存在度大于2的结点

  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

  • 任意二叉树都是由以下几种情况复合而成

    image-20230729160209984

2. 2 特殊二叉树

满二叉树:

  • 每一层节点数达到最大值,即第k层结点总数为2(k-1)
  • 通过等比数列运算,满二叉树总结点数为2k - 1。

完全二叉树:

  • 对于深度为k的完全二叉树,则前k-1层是满的
  • 最后一层从左到右是连续的

2.3 二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2(i-1)个结点.

  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2h - 1.

  3. 对任何一棵二叉树,如果度为0其叶结点个数为n0,度为2的分支结点个数为n2 ,则有n0= n2+1

  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h = log2(n+1)

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
    于序号为i的结点有:

2.4 二叉树的存储结构

二叉树一般有两种结构存储,一种顺序结构,一种链式结构

  1. 顺序结构

    适合满二叉树和完全二叉树,其他二叉树会造成空间浪费。

    image-20230729165148807

  2. 链式结构

    • 二叉链
    typedef int BTDataType;
    // 二叉链
    struct BinaryTreeNode
    {
        struct BinTreeNode* _pLeft; // 指向当前节点左孩子
        struct BinTreeNode* _pRight; // 指向当前节点右孩子
        BTDataType _data; // 当前节点值域
    };
    
    • 三叉链
    // 三叉链
    struct BinaryTreeNode
    {
        struct BinTreeNode* _pParent; // 指向当前节点的双亲
        struct BinTreeNode* _pLeft; // 指向当前节点左孩子
        struct BinTreeNode* _pRight; // 指向当前节点右孩子
        BTDataType _data; // 当前节点值域
    };
    

3 堆 — 完全二叉树的顺序结构实现

3.1 堆的概念

完全二叉树

  • 小根堆:树中所有的父亲都是小于等于孩子
  • 大根堆:树中所有的父亲都是大于等于孩子

3.2 核心代码

  • 插入时向上调整
typedef int HPDataType;
typedef struct Heap {
	HPDataType* a;
	int size;
	int capacity;
}HP;
//向上调整
void AdjustUp(HPDataType* a, int child) {
	int parent = (child - 1) / 2;
	while (child > 0) {
        // if (a[child] > a[parent]) //大根堆
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x) {
	assert(php);
	if (php->size == php->capacity) {
		//扩容
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType)*newcapacity);
		if (tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);

}
  • 删除时向下调整
//向下调整
void AdjustDown(HPDataType* a, int size, int parent) {
	int child = parent * 2 + 1;
	while(child < size)
	{
        // if (child+1 < size && a[child + 1] > a[child])  //大根堆
		if (child+1 < size && a[child + 1] < a[child]) {
			++child;
		}
        // if (a[child] > a[parent])   //大根堆
		if (a[child] < a[parent]) {
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}else{
            break;
        }
	}

}
void HeapPop(HP* php) {
	assert(php);
	assert(php->size > 0);

	Swap(php->a[0], php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);

}

3.3 堆应用

1 堆排序

升序排序使用大根堆,降序排序使用小根堆

核心代码

void HeapSort(int* a, int n) {
    
	// 向下调整建堆 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i) {
		AdjustDown(a, n, i);
	}
	int end = n - 1;
    
	// O(N * logN) 
	while (end > 0){
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}

}

时间复杂度

O(N*logN)

2 TOP-K问题

求大数据量大的前K个最大或最小元素。

  1. 直接堆排序 O(N*logN)
  2. 当数据量不是非常大时,建N个数的大堆,Top/Pop K次, O(N + logN*K)
  3. 当N非常大(100亿),K比较小(100)
    • 前K个数建立小堆
    • 剩下的N-K个一次跟堆顶数据比较,如果比堆顶数据大,就替换堆顶数据进队
    • 走完以后,堆里面的K个数,就是最大的前K个

核心代码

//模拟实现
void PrintTopK(int* a, int n, int k)
{
	// 1. 建堆--用a中前k个元素建堆
	int* kMinHeap = (int*)malloc(sizeof(int) * k);
	assert(kMinHeap);
	for (int i = 0; i < k; ++i) {
		kMinHeap[i] = a[i];
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; --i) {
		AdjustDown(kMinHeap, k, i);
	}
	// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
	for (int j = k; j < n; ++j) {
		if (a[j] > kMinHeap[0]) {
			kMinHeap[0] = a[j];
			AdjustDown(kMinHeap, k, 0);
		}
	}
	for (int i = 0; i < k; ++i) {
		printf("%d ", kMinHeap[i]);
	}
	printf("\n");
}
void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(0));
	for (int i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;

	PrintTopK(a, n, 10);
}

4 二叉树的链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

链式结构又分为二叉链和三叉链,三叉链比二叉链多一个parent指针。

image-20230804183227806

4.1 二叉链结构与初始化

typedef int BTDataType;
typedef struct BinaryTreeNode {
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

BTNode* BuyNode(BTDataType x) {
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	assert(node);
	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}

4.2 核心代码

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "Queue.h"
typedef int BTDataType;
typedef struct BinaryTreeNode {
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

BTNode* BuyNode(BTDataType x) {
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	assert(node);
	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}
BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	return node1;
}
// 前序遍历 分治
void PreOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
// 中序遍历
void InOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
		return;
	}

	PreOrder(root->left);
	printf("%d ", root->data);
	PreOrder(root->right);
}

// 后序遍历
void PostOrder(BTNode* root) {
	if (root == NULL) {
		printf("# ");
		return;
	}

	PreOrder(root->left);
	PreOrder(root->right);
	printf("%d ", root->data);

}
int count = 0;
void TreeSize(BTNode* root) {
	if (root == NULL) {
		return;
	}
	++count;
	TreeSize(root->left);
	TreeSize(root->right);
}
// 分而治之
int TreeSize2(BTNode* root) {
	return root == NULL ? 0 :
		TreeSize2(root->left) + TreeSize2(root->right) + 1;
}

int TreeLeafSize2(BTNode* root) {
	if (root == NULL)
		return 0;
	else if(root->left == NULL && root->right == NULL){
		return 1;
	}
	else {
		return TreeLeafSize2(root->left) + TreeLeafSize2(root->right);
	}
}
// 求第K层结点数
int TreeKLevel(BTNode* root, int k) {
	assert(k >= 1);
	if (root == NULL) {
		return 0;
	}
	if (k == 1) {
		return 1;
	}
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
// 求二叉树深度
int  TreePath(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	else {
		int leftDepth = TreePath(root->left);
		int rightDepth = TreePath(root->right);
		return (leftDepth > rightDepth) ? (leftDepth + 1) : (rightDepth + 1);
	}
}

// 二叉树查找值为x的结点
BTNode* TreeFind(BTNode* root, BTDataType x) {
	if (root == NULL)
		return NULL;
	if (root->data == x) {
		return root;
	}
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;

	BTNode* ret2 = TreeFind(root->left, x);
	if (ret2)
		return ret2;

	return NULL;
}

void TreeDestroy(BTNode* root) {
	if (root == NULL) {
		return;
	}
	TreeDestroy(root->left);
	TreeDestroy(root->right);
	printf("%p:%d\n", root, root->data);
	free(root);

}

//层序遍历 一种广度优先遍历 借助队列
void LevelOrder(BTNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->data);
		QueuePop(&q);
		if (front->left) {
			QueuePush(&q, front->left);
		}
		if (front->right) {
			QueuePush(&q, front->right);
		}
	}
	printf("\n");
	QueueDestroy(&q);
}

// 判断二叉树是否是完全二叉树 借助队列
bool TreeComplete(BTNode* root) {
	Queue q;
	QueueInit(&q);
	if (root) {
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front) {
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else {
			//遇到空以后,则跳出层序遍历
			break;
		}
	}
	//1、如果后面全是空,则是完全二叉树
	//2、如果空后面还有非空,则不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front) {
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

int main() {
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
	printf("\n");
	InOrder(root);
	printf("\n");
	PostOrder(root);
	printf("\n");
	printf("层序遍历:");
	LevelOrder(root);

	TreeSize(root);
	printf("TreeSize:%d\n", count);

	printf("TreeSize2:%d\n", TreeSize2(root));
	printf("leafCount:%d\n", TreeLeafSize2(root));
	printf("Tree2Level: %d\n", TreeKLevel(root, 2));
	printf("deep:%d\n", TreePath(root));

	printf("Is TreeComplete: %d\n", TreeComplete(root)); 
	TreeDestroy(root);
	root = NULL;
	return 0;
}

image-20230731170847153

5 二叉搜索树

5.1 概念

二叉搜索树又称二叉排序树,它是一棵空树或者具有以下性质的非空二叉树:

  • 若左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 搜索二叉树的左右子树也分别为二叉搜索树

image-20230801160821807

因其以上特性,使用搜索二叉树查找时,最多查找次数等于其高度。

对二叉搜索树进行中序遍历,可得到一个升序排序的数值。

5.2 结构与代码实现

#pragma once
#include <iostream>
using namespace std;
template <class K>
struct BSTreeNode {

	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		: _left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

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

		cur = new Node(key);
		if (parent->_key < key) {
			parent->_right = cur;
		}
		else {
			parent->_left = cur;
		}
		return true;
	}
	bool Find(const K& key) {
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return true;
			}
		}
		return false;
	}
	bool Erase(const K& key) { 
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key) {
				parent = cur;
				cur = cur->_left;
			}
			else {
				// 开始删除
				//1. 左为空
				//2. 右为空
				//3. 左右都不为空
				if (cur->_left == nullptr) {
					if (cur == _root) {
						_root = cur->_right;
					}
					else {
						if (cur == parent->_left) {
							parent->_left = cur->_right;
						}
						else {
							parent->_right = cur->_right;
						}
					}
					delete cur;
					cur = nullptr;
				}
				else if (cur->_right == nullptr) {
					if (_root == cur) {
						_root = cur->_left;
					}
					else {
						if (cur == parent->_left) {
							parent->_left = cur->_left;
						}
						else {
							parent->_right = cur->_left;
						}
					}
					delete cur;
					cur = nullptr;
				}
				else {
					//替换法删除
					Node* minParent = cur;
					Node* min = cur->_right;
					while (min->_left)
					{
						minParent = min;
						min = min->_left;
					}
					//cur->_key = min->_key;
					swap(cur->_key, min->_key);
					if (minParent->_left == min)
					{
						minParent->_left = min->_right;
					}
					else {
						minParent->_right = min->_right;
					}
					delete min;
				}
				return true;
			}
		}
		return false;
	}
	void InOrder() {
		_InOrder(_root);
		cout << endl;
	}

/*---------------------利用递归实现操作-------------------------------*/
	bool FindR(const K& key) {
		return _FindR(_root, key);
	}
	bool InsertR(const K& key) {
		return _InsertR(_root, key);
	}
	bool EraseR(const K& key) {
		return _EraseR(_root, key);
	}

	~BSTree() {
		_Destory(_root);
	}
	// c++11的用法,强制编译器生成默认的构造
	BSTree() = default;
	BSTree(const BSTree<K>& t) {
		_root = _Copy(t._root);
	}
	BSTree<K>& operator=(BSTree<K> t) {
		swap(_root, t._root);
		return *this;
	}
private:
	Node* _Copy(Node* root) {
		if (root == nullptr) {
			return nullptr;
		}
		Node* copyRoot = new Node(root->_key);
		copyRoot->_left = _Copy(root->_left);
		copyRoot->_right = _Copy(root->_right);
		return copyRoot;
	}
	void _Destory(Node*& root) {
		if (root == nullptr) {
			return;
		}
		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
		root = nullptr;
	}
	bool _EraseR(Node*& root, const K& key) {
		if (root == nullptr) {
			return false;
		}
		if (root->_key < key) {
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key) {
			return _EraseR(root->_left, key);
		}
		else {
			Node* del = root;
			if (root->_left == nullptr) {
				root = root->_right;
			}else if (root->_right == nullptr) {
				root = root->_left;
			}
			else {
				// 找右树的最左节点
				Node* min = root->_right;
				while (min->_left) {
					min = min->_left;
				}
				swap(root->_key, min->_key);
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}
	bool _InsertR(Node* &root, const K& key) {
		if (root == nullptr) {
			root = new Node(key);
			return true;
		}
		if (root->_key < key) {
			return _InsertR(root->_right, key);
		}
		else if (root->_key > key) {
			return _InsertR(root->_left, key);
		}
		else {
			return false;
		}
	}
	bool _FindR(Node* root, const K& key) {
		if (root == nullptr) {
			return false;
		}
		if (root->_key < key) {
			return _FindR(root->_right);
		}
		else if (root->_key > key) {
			return _FindR(root->_left, key);
		}
		else {
			return true;
		}
	}
	void _InOrder(Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;

};
void TestBSTree1() {
	BSTree<int> t;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto e : a) {
		t.Insert(e);
	}
	// 排序+去重
	t.InOrder();

	t.EraseR(3);
	t.InOrder();

	t.EraseR(8);
	t.InOrder();

	for (auto e : a) {
		t.EraseR(e);
		t.InOrder();
	}
}
void TestBSTree3() {
	BSTree<int> t;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto e : a) { 
		t.InsertR(e);
	}
	BSTree<int> copy = t;
	copy.InOrder();
	t.InOrder();

	BSTree<int> t1;
	t1.Insert(2);
	t1.Insert(1);
	t1.Insert(3);

	copy = t1;
	copy.InOrder();
	t1.InOrder();
}

5.3 复杂度与缺陷

二叉搜索树的增删查的时间复杂度为O(h),h为树的高度。当key值的插入接近有序时,h最坏情况下等于N。此时增删查时间复杂度过高。

平衡树(AVL树)改善了此处的缺陷。

6 AVL树

6.1 概念

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

AVL树,或者是颗空树,或者是具有下列性质的二叉搜索树:

  • 左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

image-20230801192637797

6.2 旋转核心代码与思想

单旋:

image-20230803144908539

//左单旋
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 (_root == parent)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR;
        }
        else
        {
            ppNode->_right = subR;
        }

        subR->_parent = ppNode;
    }

    subR->_bf = parent->_bf = 0;
}

image-20230803145136657

//右单旋
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 (_root == parent)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }

        subL->_parent = ppNode;
    }

    subL->_bf = parent->_bf = 0;
}

左右双旋

  1. b插入新增,引发双旋

image-20230803195834459

  1. c插入新增,引发双旋

image-20230803195859083

  1. a/b/c/d是空树,60是新增,引发双旋

image-20230803195930790

void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;

    RotateL(parent->_left);
    RotateR(parent);

    subLR->_bf = 0;
    if (bf == 1)
    {
        parent->_bf = 0;
        subL->_bf = -1;
    }
    else if (bf == -1)
    {

        parent->_bf = 1;
        subL->_bf = 0;
    }
    else if (bf == 0)
    {
        parent->_bf = 0;
        subL->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
//右左双旋,与左右双旋相似,图略
void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

    int bf = subRL->_bf;

    RotateR(parent->_right);
    RotateL(parent);

    subRL->_bf = 0;
    if (bf == 1)
    {
        subR->_bf = 0;
        parent->_bf = -1;
    }
    else if (bf == -1)
    {
        subR->_bf = 1;
        parent->_bf = 0;
    }
    else if (bf == 0)
    {
        parent->_bf = 0;
        subR->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

旋转的价值和意义:

  1. 平衡
  2. 降高度(高度恢复到插入之前的样子)

6.3 插入

bool Insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        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);
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }

    cur->_parent = parent;

    // 控制平衡
    // 1、更新平衡因子
    while (parent)
    {
        if (cur == parent->_right)
        {
            parent->_bf++;
        }
        else
        {
            parent->_bf--;
        }

        if (parent->_bf == 0)
        {
            break;
        }
        else if (abs(parent->_bf) == 1)
        {
            parent = parent->_parent;
            cur = cur->_parent;
        }
        else if (abs(parent->_bf) == 2)
        {
            // 说明parent所在子树已经不平衡了,需要旋转处理
            if (parent->_bf == 2 && cur->_bf == 1)
            {
                RotateL(parent);
            }
            else if ((parent->_bf == -2 && cur->_bf == -1))
            {
                RotateR(parent);
            }
            else if (parent->_bf == -2 && cur->_bf == 1)
            {
                RotateLR(parent);
            }
            else if (parent->_bf == 2 && cur->_bf == -1)
            {
                RotateRL(parent);
            }
            else
            {
                assert(false);
            }

            break;
        }
        else
        {
            assert(false);
        }
    }

    return true;
}

6.4 验证平衡

bool _IsBanlance(Node* root) {
    if (root == nullptr) {
        return true;
    }
    int leftHT = Height(root->_left);
    int rightHT = Height(root->_right);
    int diff = rightHT - leftHT;

    return abs(diff) < 2
        && _IsBanlance(root->_left)
        && _IsBanlance(root->_right);
}
int Height(Node* root) {
    if (root == nullptr)
        return 0;
    return max(Height(root->_left), Height(root->_right)) + 1;
}

7 红黑树

7.1 红黑树的概念

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

效果:相对而言,插入同样的数据,AVL树旋转更多,红黑树旋转更少

image-20230804120722687

7.2 红黑树的性质

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

由以上性质可知,一棵树内极限最短为全黑,极限最长为一黑一红,因此最长路径中节点个数不会超过最短路径节点个数的两倍。

7.3 插入

插入思想

  1. cur->_parent为黑,插入成功

  2. cur->_parent为红,分情况进行调整:

    • 情况一:cur为红,p为红,g为黑,u存在且为红:

      image-20230804150034369

      调整后,若g为根节点,此时只需将g变为黑色即可调整完毕。

      调整后,若g不为根节点,且g的父结点为黑色则调整完毕;若g的父结点为红色,则将g作为当前节点继续根据情况调整。

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

      cur和p都是他们的父结点的左孩子,对g进行右单旋,p变黑,g变红,则调整完毕:

      image-20230804164710743

      cur和p都是他们的父结点的右孩子,对g进行左单旋,p变黑,g变红,则调整完毕:

      image-20230804164537086

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

      p为g的左孩子,cur为p的右孩子,则针对p做左单旋,则可转为情况二继续调整:

      image-20230804152710912

      p为g的右孩子,cur为p的左孩子,则针对p做右单旋,则可转为情况二继续调整:

      image-20230804153722803

      则情况三中,无论是上述哪两种小情况,都需要进行一次单旋后转为情况二继续调整,那么情况三总的调整也就是双旋后进行变色(单旋+情况二的单旋+变色)。

红黑树的的关键是看叔叔。

核心代码

bool Insert(const pair<K, V>& kv)
{
    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);
        assert(grandfather->_col == BLACK);
        // 关键看叔叔
        if (parent == grandfather->_left)
        {
            Node* uncle = grandfather->_right;
            // 情况一:uncle存在且为红,变色+继续往上处理
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                // 继续将grandfather作为current进行处理
                cur = grandfather;
                parent = cur->_parent;
            }
            // 情况二+三:uncle不存在或uncle存在为黑色
            else
            {
                // 情况二:单旋+变色
                //     g
                //   p   u
                // c
                if (cur == parent->_left)
                {
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else
                {
                    // 情况三:左单旋+右单旋+变色
                    //     g
                    //   p   u
                    //     c
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
        else
        {
            Node* uncle = grandfather->_left;
            // 情况一:叔叔是红色
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                // 继续将grandfather作为current进行处理
                cur = grandfather;
                parent = cur->_parent;
            }
            // 情况二+三:uncle不存在或uncle存在为黑色
            else
            {
                // 情况二:单旋+变色
                //     g
                //   u   p
                //         c
                if (cur == parent->_right)
                {
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                else
                {
                    // 情况三:右单旋+左单旋+变色
                    //     g
                    //   u   p
                    //     c
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    _root->_col = BLACK;
    return true;
}

7.4 验证红黑树

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool IsBanlance()
{
    if (_root == nullptr)
    {
        return true;
    }
    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 PrevCheck(_root, 0, benchmark);
}
bool PrevCheck(Node* root, int blackNum, int benchmark)
{
    if (root == nullptr)
    {
        /*cout << blackNum << endl;
            return;*/
        if (blackNum != benchmark)
        {
            cout << "某条黑色节点的数量不相等" << endl;
            return false;
        }
        else {
            return true;
        }
    }
    if (root->_col == RED && root->_parent->_col == RED)
    {
        cout << "存在红色的连续节点" << endl;
        return false;
    }
    if (root->_col == BLACK)
    {
        ++blackNum;
    }
    return PrevCheck(root->_left, blackNum, benchmark)
        && PrevCheck(root->_right, blackNum, benchmark);
}



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

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

相关文章

大道至简,炎凰数据要做极致好用的国产大数据分析基础软件|爱分析调研

自2000年代初大数据技术诞生以来&#xff0c;为了应对不断丰富的应用场景、日益复杂的数据类型&#xff0c;以及逐渐膨胀的数据规模&#xff0c;大数据业内逐渐发展出了多种技术路线。 到今天&#xff0c;大数据产品和技术已处于百花齐放的状态&#xff0c;国内市场近年来也因…

.Net6 Web Core API --- AOP -- log4net 封装 -- MySQL -- txt

目录 一、引入 NuGet 包 二、配置log4net.config 三、编写Log4net封装类 四、编写日志记录类 五、AOP -- 拦截器 -- 封装 六、案例编写 七、结果展示 一、引入 NuGet 包 log4net Microsoft.Extensions.Logging.Log4Net.AspNetCore MySql.Data ---- MySQL…

好用的智能ai绘画文字转绘画软件大揭秘

在一个平凡的小镇上&#xff0c;有一位名叫李彤的年轻画家。她对绘画充满了热爱和渴望。但因为经济条件有限&#xff0c;无法承担昂贵的绘画软件费用。然而&#xff0c;就在她感到困惑和单调的文字转换为令人惊叹的绘画作品&#xff0c;让每一位渴望艺术表达的人都能轻松实现自…

css实现卡片的左上角有一个三角形的遮盖效果

需求: 卡片的左上角有一个绿色的三角形标签,用来区分状态 实现: .vCard{position: relative;overflow: hidden; } .vCard::before {content: "";position: absolute;top: 0;left: 0;width: 0;height: 0;border-bottom: 20px solid transparent;border-left: 20px …

直线模组在机械手中的应用

机械手是一种能模仿人手和臂的某些动作功能&#xff0c;用以按固定程序抓取、搬运物件或操作工具的自动操作装置&#xff0c;可代替人的繁重劳动以实现生产的机械化和自动化&#xff0c;能在有害环境下操作以保护人身安全&#xff0c;因而广泛应用于机械制造、冶金、电子、轻工…

创新赛制,顶尖赛题!| 2023第二届粤港澳大湾区(黄埔)国际算法算例大赛十赛道虚位以待!

2023 第二届“粤港澳大湾区&#xff08;黄埔&#xff09;国际算法算例大赛”&#xff08;以下简称“大赛”&#xff09;于 2023 年 7 月 15 日正式开赛。大赛面向全球&#xff0c;诚邀有创新精神&#xff0c;并有较好的AI算法算例基础的高校学生、AI领域相关企业和研究院所的从…

硬件串口通信协议学习(UART、IIC、SPI、CAN)

0.前言 学习资料&#xff1a;江协科技的个人空间-江协科技个人主页-哔哩哔哩视频 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 全双工&#xff1a;通信…

寄件管理系统设置教程

“企业寄件管理系统”或许是个小众词汇&#xff0c;但是“企业寄件”却是各家公司都不陌生的词汇。在经济和快递发展的双重影响之下&#xff0c;企业寄件早已成为企业运转的日常事项之一&#xff0c;企业寄件管理也越发被企业管理者所重视。我们对企业管理系统并不陌生&#xf…

go 基本语法(简单案例)

&#xff01;注&#xff1a; go中 对变量申明很是严格&#xff0c;申明了&#xff0c;在没有使用的情况下&#xff0c;也会产生编译错误 1.行分隔符 一行就是代码&#xff0c;无&#xff1b;分割&#xff0c;如果需要在一行展示&#xff0c;需要以&#xff1b;分割&#xff0c;…

短信端口和端口短信的概念

什么是端口短信 端口短信是针对点到点短信来说的,点到点即手机直接发给手机的,端口就是使用软件发给手机的。软件是移动运营商合作的内容提供商开发的,遵守运营商的规范,有端口号。 短信端口是什么意思? 是针对点到点短信来说的,点对点即手机直接发给手机的,端口就是使用软…

C# PaddleDetection 版面分析

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Lin…

java:JUnit单元测试

背景 一般软件测试分为白盒测试和黑盒测试 黑盒测试&#xff1a;就是现在一般测试人员的工作点点点&#xff0c;只要功能正常就行白盒测试&#xff1a;需要关注代码的具体执行过程&#xff0c;是需要写代码的 JUnit 框架是进行白盒测试使用的。 不使用单元测试框架 没有单…

看一家头部实干派互娱公司如何玩转DataOps?

DataOps的概念自首次被提出至今已有8年&#xff0c;并在2018年被Gartner纳入数据管理技术成熟度曲线。从实施上看&#xff0c;当下DataOps仍处在发展初期&#xff0c;鲜少企业或团队能据此真正沉淀一套方法论或技术产品的体系。不过&#xff0c;随着越来越多的企业开启DataOps实…

【flink】使用flink-web-ui提交作业报错

使用WebUI提交作业出现错误。 错误截图&#xff1a; 弹框信息&#xff1a; Server Response Message: org.apache.flink.runtime.rest.handler.RestHandlerException: Could not execute application.at org.apache.flink.runtime.webmonitor.handlers.JarRunHandler.lambda$h…

行业追踪,2023-08-04

自动复盘 2023-08-04 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

idea使用protobuf

本文参考&#xff1a;https://blog.csdn.net/m0_37695902/article/details/129438549 再次感谢分享 什么是 protobuf &#xff1f; Protocal Buffers(简称protobuf)是谷歌的一项技术&#xff0c;用于结构化的数据序列化、反序列化。 由于protobuf是跨语言的&#xff0c;所以用…

进程与线程、线程创建、线程周期、多线程安全和线程池(ThreadPoolExecutor)

目录 进程与线程线程和进程的区别是什么&#xff1f;线程分两种&#xff1a;用户线程和守护线程线程创建四种方式run()和start()方法区别&#xff1a;为什么调用 start() 方法时会执行 run() 方法&#xff0c;为什么不能直接调用 run() 方法&#xff1f;Runnable接口和Callable…

最强实战,接口自动化测试Python3+Requests+Unittest+ddt框架封装(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口自动化测试框…

RDP远程桌面服务的RD授权过期解决方法

RDP远程桌面服务的RD授权过期解决方法 一、打开远程桌面会话主机配置二、打开远程桌面授权模式属性三、选择远程桌面授权模式四、添加许可证服务器五、选择确定保存设置六、RD授权设置成功七、查看“授权诊断” 一、打开远程桌面会话主机配置 二、打开远程桌面授权模式属性 双…

35岁,不是体能衰老的分界线!

35岁&#xff0c;不是体能衰老的分界线 1. 35岁以上求职者同比增长14.9%&#xff0c;体能下滑成为35岁以上年龄群体的标签。 2. 35岁人群开始感觉经常失眠、腰背痛和肥胖&#xff0c;体力不济可能是常年累积的不规律作息和饮食习惯导致。 3. 35岁以后体能下滑&#xff0c;无法高…