查找树(BST到2-3树再到红黑树)

Posted R_Arisa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了查找树(BST到2-3树再到红黑树)相关的知识,希望对你有一定的参考价值。

二叉查找树(BST)

思路

二叉查找树保证任意一个结点的左结点都小于它,而右结点都大于它。

  • 插入
    插入一个结点时首先查找是否有相同的键,若有则更新即可。
    否则直到查找到应该插入的点,创建结点并使得其父结点指向它。
  • 查找
    递归地查找根结点的子结点,直到找到。
  • 删除
    删除是BST最复杂的操作。

若待删除结点是叶子结点,那么只需简单地删掉即可。
若待删除结点只有一个子结点,那么只需用其子结点代替自己在树中的位置即可。
最复杂的是待删除结点有两个子结点的情况,这种情况需要使用其后继结点来代替它的位置,这样的话就可以保持BST的有序性。

所谓后继结点,就是指待删除结点右子树中的最小结点。删除的步骤可以分为以下:

  1. 找到待删除结点node。
  2. 找到其后继结点sub。
  3. 用sub代替node的位置。
  4. 在node的右子树中删除sub结点

对比自己写的C++代码和书中的Java代码,感觉C++好麻烦…

评估

因为插入和查找的行为几乎一致,因此时间复杂度也相同,取决于树的形状。
最坏的情况是有N层,就像一个数组,其时间复杂度为 O ( N ) O(N) O(N)。而最好的情况则是一颗平衡二叉树,其时间复杂度为 O ( l o g N ) O(logN) O(logN)
查找所需的平均比较次数为 2 l n N 2lnN 2lnN

代码

/*
 * Key min()							返回最小键
 * Key max()							返回最大键
 * Key floor(Key key)					返回key向下取整的键
 * Key celling(Key key)					返回key向上取整了键
 * Key select(int k)					返回排名为k的键(有k个键比它小)
 * int rank(Key key)					返回key的排名
 * void deleteMin()						删除最小键
 * void Delete(Key key)					删除任意键
 * void print()							中序遍历,按升序打印键
 * vector<Key> &range(Key lo, Key hi)	返回一个存储范围内键的vector
 */

template<typename Key, typename Value>
struct Node 
	Key key;
	Value val;
	Node *left;
	Node *right;
	int N;	// 以该结点为根的树中结点的总数
	Node(Key key, Value val, int N) 
		this->key = key;
		this->val = val;
		this->N = N;
		this->left = nullptr;
		this->right = nullptr;
	
;

template<typename Key, typename Value>
class BST 
private:
	Node<Key, Value> *root;
	int size(Node<Key, Value> *root) 
		if (root == nullptr)
			return 0;
		else
			return root->N;
	
	Value get(Node<Key, Value> *node, Key key) 
		if (node == nullptr)
			return nullptr;
		if (node->key == key)
			return node->val;
		else if (key < node->key)
			return get(node->left, key);
		else
			return get(node->right, key);
	
	Node<Key, Value> *put(Node<Key, Value> *node, Key key, Value val) 
		// 不存在时,建立新结点
		if (node == nullptr) 
			return new Node<Key, Value>(key, val, 1);
		
		// 如果存在,则更新
		if (node->key == key)
			node->val = val;
		// 否则查找
		else if (key < node->key)
			node->left = put(node->left, key, val);
		else
			node->right = put(node->right, key, val);
		updateN(root);
		return node;
	
	Node<Key, Value> *select(Node<Key, Value> *node, int k) 
		if (k < size(node->left))
			return select(node->left, k);
		else if (k > size(node->left))
			return select(node->right, k - size(node->left) - 1);
		else
			return node;
	
	int *rank(Node<Key, Value> *node, Key key) 
		if (key == node->key)
			return size(node->left);
		else if (key < size(node->left))
			return rank(node->left, key);
		else
			return rank(node->right, key) + size(node->left) + 1;
	
	int updateN(Node<Key, Value> *node) 
		if (node == nullptr)
			return 0;
		else
			node->N = updateN(node->left) + updateN(node->right) + 1;
	
	void print(Node<Key, Value> *node) 
		if (node == nullptr)
			return;
		print(node->left);
		std::cout << node->key << " ";
		print(node->right);
	
	void range(std::vector<Key> &arr, Node<Key, Value> *node, Key lo, Key hi) 
		if (node == nullptr)
			return;
		if (lo < node->key)
			range(arr, node->left, lo, hi);
		if (lo <= node->key && node->key <= hi)
			arr.push_back(node->key);
		if (node->key < hi)
			range(arr, node->right, lo, hi);
	
	Node<Key, Value> *Delete(Node<Key, Value> *node, Key key) 
		if (node == nullptr)
			return nullptr;
		if (key < node->key)
			node->left = Delete(node->left, key);
		else if (key > node->key)
			node->right = Delete(node->right, key);
		else if (key == node->key) 
			if (node->left != nullptr && node->right != nullptr) 
				// 左右子结点都不为空
				Node<Key, Value> *DeletedRight = node->right;
				// 找到后继结点sub(subsequent)
				Node<Key, Value> *sub = DeletedRight;
				Node<Key, Value> *pre = node;
				while (sub->left != nullptr) 
					pre = sub;
					sub = sub->left;
				
				// 让后继结点代替deleted的位置
				// sub和右结点是同一结点时需要特殊处理
				if (sub == DeletedRight) 
					sub->left = node->left;
					delete node;
					return sub;
				
				else 
					sub->right = DeletedRight;
					sub->left = node->left;
					pre->left = nullptr;
					delete node;
					return sub;
				
			
			else if (node->left == nullptr && node->right == nullptr) 
				delete node;
				return nullptr;
			
			else if (node->left == nullptr) 
				Node<Key, Value> *newRight = node->right;
				delete node;
				return newRight;
			
			else if (node->right == nullptr) 
				Node<Key, Value> *newLeft = node->left;
				delete node;
				return newLeft;
			
		
		return node;
	
	// 用于析构函数
	void destructor(Node<Key, Value> *node) 
		if (node == nullptr)
			return;
		destructor(node->left);
		destructor(node->right);
		delete node;
	
public:
	BST() 
		root = nullptr;
	
	~BST() 
		destructor(root);
	
	int size() 
		return size(root);
	
	Value get(Key key) 
		return get(root, key);
	
	void put(Key key, Value val) 
		root = put(root, key, val);
	
	Key min() 
		Node<Key, Value> *cur = root;
		while (cur->left != nullptr)
			cur = cur->left;
		return cur->key;
	
	Key max() 
		Node<Key, Value> *cur = root;
		while (cur->right != nullptr)
			cur = cur->right;
		return cur->key;
	
	Key floor(Key key) 
		Node<Key, Value> *cur = root;
		while (cur->left != nullptr && key < cur->key)
			cur = cur->left;
		if (cur->right != nullptr && cur->right->key <= key)
			return cur->right->key;
		else
			return cur->key;
	
	Key celling(Key key) 
		Node<Key, Value> *cur = root;
		while (cur->right != nullptr && key > cur->key)
			cur = cur->right;
		if (cur->left != nullptr && cur->left->key >= key)
			return cur->left->key;
		else
			return cur->key;
	
	Key select(int k) 
		return select(root, k)->key;
	
	int rank(Key key) 
		return rank(root, key);
	
	void deleteMin() 
		Node<Key, Value> *pre = root;
		Node<Key, Value> *cur = root->left;
		if (cur == nullptr) 
			if (root->right != nullptr) 
				root = root->right;
				delete pre;
			
			else 
				delete root;
				root = nullptr;
			
			updateN(root);
			return;
		
		while (cur->left != nullptr) 
			cur = cur->left;
			pre = pre->left;
		
		pre->left = cur->right;
		delete cur;
		updateN(root);
	
	void Delete(Key key) 
		root = Delete(root, key);
		updateN(root);
	
	void print() 
		print(root);
	
	std::vector<Key> range(Key lo, Key hi) 
		std::vector<Key> arr;
		range(arr, root, lo, hi);
		return arr;
	
;

2-3查找树

BST的形状和输入相关,如果想要最坏情况下也只需要对数级别的查找,就需要一个平衡二叉树。下面介绍的2-3查找树就是一颗完美平衡二叉树(即所有空链接到根结点的距离都相等)。

如果说BST的结点是2-结点,即每个结点有两条链接、一个键,那么2-3查找树就是再引入了3-结点,即有三条链接和2个键的结点。它的左链接的键小于该结点,右链接的键大于该结点,中间链接的键则在该结点两个键之间。
对于2-3查找树的构造,可以分为以下几种情况:

  • 向2-结点中插入新键
    只需将其替换为3-结点即可。
  • 向3-结点中插入新键
    直接插入使其暂时成为一个4-结点,然后将中间大小的键插入到其父结点中,然后把该3-结点分成2个2-结点。如果此时其父结点变成了4-结点,就重复该操作。如果直到根结点变成了4-结点,就将其中间键提出来作为根结点。

构造结果和输入顺序有关,但都满足定义。

注意如果不把4-结点划分成2个2-结点,则构造后可能不满足“任意空链接到根结点的距离相等”,也可能构造出不符合大小顺序的2-3查找树。如下例:

4 19 1 3 8 18 24

给该2-3查找树插入13得:

4 19

红黑树理解(一) 从2-3树到红黑树

红黑树理解 (一) 从2-3树到红黑树
红黑树理解(二)插入过程图解
红黑树理解(三)变色

二叉搜索树(Binary Search Tree)

 二叉搜索树,也叫二叉排序树,简称BST。如果连续升序插入,或者连续降序插入,二叉搜索树就类似于一个链表了,这时的查询也从O( l o g 2 n log_2n log2n)退化到了O(n)。例如:

如何解决二叉搜索树存在的不平衡问题?

在插入数据时,进行旋转。
也就是平衡二叉搜索树,常见的平衡二叉搜索树有AVL树、红黑树。对于AVL和红黑树孰优孰劣,有人认为差不多,有人认为红黑树综合性能比AVL要好,参考:avl 树和 rbtree 差不多。谁好谁不好不重要。本文主要分析红黑树,为将来的HashMap源码分析做铺垫。

红黑树中红色结点有什么特殊含义吗?

红黑树中的红色结点,来源于2-3树。2-3树并不是二叉查找树,而是多路查找树。2-3树中,一个3-Node结点可以拆分成2个2-Node结点,这两个2-Node结点,用一条红色的边连接起来。
 但是BST是没有边的定义的,所以这个颜色只能记录在节点的数据结构中,所以我们把从父节点到子节点的红边会存储在子节点中——将子节点涂成红色,构成红色节点。其他的自然都是黑色节点。
关于2-3树和红黑树的更具体的分析,参考:换个角度彻底理解红黑树

红黑树举例

https://rbtree.phpisfuture.com/上,可以直接构建一刻红黑树。以文章开头那个倾斜的二叉搜索树为例,看看连续插入8,7,6,5,4,3会得到怎样的红黑树。

可以看到,从根节点到叶子结点,所有路径都只有2个黑色结点(不算叶子结点)。

以上是关于查找树(BST到2-3树再到红黑树)的主要内容,如果未能解决你的问题,请参考以下文章

从2-3-4树模型到红黑树实现

红黑树理解(一) 从2-3树到红黑树

红黑树一:从二叉树2-3树到红黑树,一步步讲解红黑树的来源

红黑树的条件是怎么来的——从2-3树到红黑树

从2-3树到红黑树,BB+B树

从红黑树的本质出发,彻底理解红黑树!