二叉查找树保证任意一个结点的左结点都小于它,而右结点都大于它。
若待删除结点是叶子结点,那么只需简单地删掉即可。
若待删除结点只有一个子结点,那么只需用其子结点代替自己在树中的位置即可。
最复杂的是待删除结点有两个子结点的情况,这种情况需要使用其后继结点来代替它的位置,这样的话就可以保持BST的有序性。
所谓后继结点,就是指待删除结点右子树中的最小结点。删除的步骤可以分为以下:
对比自己写的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;
}
};
BST的形状和输入相关,如果想要最坏情况下也只需要对数级别的查找,就需要一个平衡二叉树。下面介绍的2-3查找树就是一颗完美平衡二叉树(即所有空链接到根结点的距离都相等)。
如果说BST的结点是2-结点,即每个结点有两条链接、一个键,那么2-3查找树就是再引入了3-结点,即有三条链接和2个键的结点。它的左链接的键小于该结点,右链接的键大于该结点,中间链接的键则在该结点两个键之间。
对于2-3查找树的构造,可以分为以下几种情况:
构造结果和输入顺序有关,但都满足定义。
注意如果不把4-结点划分成2个2-结点,则构造后可能不满足“任意空链接到根结点的距离相等”,也可能构造出不符合大小顺序的2-3查找树。如下例:
给该2-3查找树插入13得:
将13插入其父结点得到:
而非:
这样的话最终得到的结果就是:
而非:
很显然结点19有一个空链接和其他空链接距离不等。且此时18在13的左子树。
利用2-3查找树来定义红黑树,就是将所有3-结点分成两个2-结点,并将其用一条红链接连接。其他普通的2-结点则用黑链接连接。将所有红链接拉平,红黑树就变成了2-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;
}
};