查找树(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 &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
1 3
8 13 18
24

将13插入其父结点得到:

4 13 19
1 3
8
18
24

而非:

4 13 19
1 3
8 18
24

这样的话最终得到的结果就是:

13
4
19
1 3
8
18
24

而非:

13
4
19
1 3
8 18
24

很显然结点19有一个空链接和其他空链接距离不等。且此时18在13的左子树。

红黑树

利用2-3查找树来定义红黑树,就是将所有3-结点分成两个2-结点,并将其用一条红链接连接。其他普通的2-结点则用黑链接连接。将所有红链接拉平,红黑树就变成了2-3查找树。
红黑树自己的定义则是:

  • 红链接均为左链接
  • 没有任何一个结点同时和2条红链接相连
  • 红黑树是完美黑色平衡的,即任意空链接到根结点所经过的黑链接数相等。

  • 旋转
    红黑树需要一个将红链接旋转的操作,用以支持插入操作。
  • 插入
    类似于2-3树在插入时总是先把待插入的元素放到一个结点中,再将其取出放到正确位置。红黑树在插入时也总是插入一条红链接,然后进行后续操作。
    若插入的是2-结点,则只需简单地旋转即可。
    若插入的是3-结点(即插入后有了两条相连的红链接),则需要进行颜色转换。
    最终,插入操作可以归纳为以下3种情况:
    • 若右结点是红色而左结点是黑色,则左旋转。(或其对称情况)
    • 若左结点和左结点的左结点都是红色,则右旋转。(或其对称情况)
    • 若左右结点都是红色,则颜色转换。

注意顺序不能颠倒,后面的每一种情况都可能是前一种情况的结果。

另外,每当由于插入操作使得根结点变红时,都需要将其恢复为黑色,此时数的高度+1。

评估

红黑树的所有操作都是对数级别的,只有范围查找除外(它需要的额外时间和返回的键的数量成正比)。

代码

const bool RED = true;
const bool BLACK = false;

template<typename Key, typename Value>
struct Node {
	Key key;
	Value val;
	Node *left;
	Node *right;
	int N;
	// 一个结点的颜色是其与其父结点的链接的颜色
	bool color;	// true表示红,false表示黑

	Node(Key _key, Value _val, int N, bool color) : key(_key), val(_val) {
		this->N = N;
		this->color = color;
	}
};

template<typename Key, typename Value>
class RedBlackBST {
private:
	Node<Key, Value> *root;

	int size(Node<Key, Value> *node) { return node ? node->N : 0; }
	bool isRed(Node<Key, Value> *node) { return node ? node->color : false; }
	// 将node结点的右红链接左旋转
	Node<Key, Value> *rotateLeft(Node<Key, Value> *node) {
		Node<Key, Value> *nodeRight = node->right;
		node->right = nodeRight->left;
		nodeRight->left = node;
		nodeRight->color = node->color;
		node->color = RED;
		nodeRight->N = node->N;
		node->N = 1 + size(node->left) + size(node->right);
		return nodeRight;
	}
	Node<Key, Value> *rotateRight(Node<Key, Value> *node) {
		Node<Key, Value> *nodeLeft = node->left;
		node->left = nodeLeft->right;
		nodeLeft->right = node;
		nodeLeft->color = node->color;
		node->color = RED;
		nodeLeft->N = node->N;
		node->N = 1 + size(node->left) + size(node->right);
		return nodeLeft;
	}
	
	// 将node的两个红链接转为黑色,将node的链接转为红色
	void flipColor(Node<Key, Value> *node) {
		node->color = RED;
		node->left->color = BLACK;
		node->right->color = BLACK;
	}

	Node<Key, Value> *put(Node<Key, Value> *node, Key key, Value val) {
		if (node == nullptr)
			return new Node(key, val, 1, RED);
		if (key < node->key)
			node->left = put(node->left, key, val);
		else if (key > node->key)
			node->right = put(node->right, key, val);
		else
			node->val = val;

		// 注意下面的顺序不能颠倒,后面的每一种情况都可能是前一种情况的结果

		// 当红链接是右链接时,左旋转
		if (isRed(node->right) && !isRed(node->left))
			node = rotateLeft(node);
		// 当左子结点和它的左子结点都是红链接时,右旋转
		if (isRed(node->left) && isRed(node->left->left))
			node = rotateRight(node);
		// 当左右子结点都是红链接时
		if (isRed(node->left) && isRed(node->right))
			flipColor(node);

		node->N = 1 + size(node->left) + size(node->right);
		return node;
	}

public:
	void put(Key key, Value val) {
		root = put(root, key, val);
		root->color = BLACK;
	}
};

你可能感兴趣的:(算法与数据结构,数据结构,c++,BST,红黑树)