引言
二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,每个节点的左子树节点值小于根节点值,而右子树节点值大于根节点值。二叉搜索树在计算机科学中有着广泛的应用,尤其在动态查找表和优先队列的实现中。本文将详细介绍二叉搜索树的定义、基本操作及平衡二叉树(AVL树和红黑树)。
二叉搜索树的定义和操作
定义
二叉搜索树是一种节点值满足特定排序的二叉树,定义如下:
- 每个节点都有一个值。
- 左子树中所有节点的值都小于根节点的值。
- 右子树中所有节点的值都大于根节点的值。
- 左右子树也分别是二叉搜索树。
基本操作
插入操作
插入操作是向二叉搜索树中添加一个新节点。根据新节点的值与当前节点的值比较,决定插入左子树或右子树。以下是插入操作的代码示例:
java
class TreeNode {
int value;
TreeNode left, right;
// 构造函数
TreeNode(int item) {
value = item;
left = right = null;
}
}
class BinarySearchTree {
TreeNode root;
BinarySearchTree() {
root = null;
}
// 插入值到二叉搜索树
void insert(int value) {
root = insertRec(root, value);
}
// 递归方式插入值
TreeNode insertRec(TreeNode root, int value) {
// 如果树为空,则创建新节点
if (root == null) {
root = new TreeNode(value);
return root;
}
// 如果值小于根节点,则插入左子树
if (value < root.value)
root.left = insertRec(root.left, value);
// 如果值大于根节点,则插入右子树
else if (value > root.value)
root.right = insertRec(root.right, value);
// 返回(未变更的)节点指针
return root;
}
// 中序遍历打印树
void inorder() {
inorderRec(root);
}
// 递归方式中序遍历树
void inorderRec(TreeNode root) {
if (root != null) {
inorderRec(root.left);
System.out.print(root.value + " ");
inorderRec(root.right);
}
}
// 测试插入操作
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
tree.insert(50);
tree.insert(30);
tree.insert(20);
tree.insert(40);
tree.insert(70);
tree.insert(60);
tree.insert(80);
// 打印插入后的树
System.out.print("中序遍历结果: ");
tree.inorder(); // 输出:20 30 40 50 60 70 80
}
}
插入操作图解
- 初始化一个空树。
- 插入第一个值
50
,成为根节点。 - 插入值
30
,比50
小,插入到50
的左子树。 - 插入值
20
,比30
小,插入到30
的左子树。 - 插入值
40
,比30
大,插入到30
的右子树。 - 插入值
70
,比50
大,插入到50
的右子树。 - 插入值
60
,比70
小,插入到70
的左子树。 - 插入值
80
,比70
大,插入到70
的右子树。
plaintext
50
/ \
30 70
/ \ / \
20 40 60 80
20 30 40 50 60 70 80
删除操作
删除操作是从二叉搜索树中移除一个节点。根据要删除节点的位置及其子节点情况进行相应处理。以下是删除操作的代码示例:
java
class BinarySearchTree {
TreeNode root;
BinarySearchTree() {
root = null;
}
// 删除指定值的节点
void deleteKey(int value) {
root = deleteRec(root, value);
}
// 递归方式删除值
TreeNode deleteRec(TreeNode root, int value) {
// 基本情况:树为空
if (root == null) return root;
// 递归查找要删除的节点
if (value < root.value)
root.left = deleteRec(root.left, value);
else if (value > root.value)
root.right = deleteRec(root.right, value);
else {
// 节点只有一个子节点或没有子节点
if (root.left == null)
return root.right;
else if (root.right == null)
return root.left;
// 节点有两个子节点,获取右子树中最小值
root.value = minValue(root.right);
// 递归删除右子树中的最小值节点
root.right = deleteRec(root.right, root.value);
}
return root;
}
// 查找最小值
int minValue(TreeNode root) {
int minValue = root.value;
while (root.left != null) {
minValue = root.left.value;
root = root.left;
}
return minValue;
}
// 中序遍历打印树
void inorder() {
inorderRec(root);
}
// 递归方式中序遍历树
void inorderRec(TreeNode root) {
if (root != null) {
inorderRec(root.left);
System.out.print(root.value + " ");
inorderRec(root.right);
}
}
// 测试删除操作
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
tree.insert(50);
tree.insert(30);
tree.insert(20);
tree.insert(40);
tree.insert(70);
tree.insert(60);
tree.insert(80);
System.out.println("删除前的树:");
tree.inorder(); // 输出:20 30 40 50 60 70 80
tree.deleteKey(20);
System.out.println("\n删除20后的树:");
tree.inorder(); // 输出:30 40 50 60 70 80
tree.deleteKey(30);
System.out.println("\n删除30后的树:");
tree.inorder(); // 输出:40 50 60 70 80
tree.deleteKey(50);
System.out.println("\n删除50后的树:");
tree.inorder(); // 输出:40 60 70 80
}
}
删除操作图解
假设我们要从上面的树中删除值 50
、30
和 20
:
- 删除
20
:节点20
没有子节点,直接删除。 - 删除
30
:节点30
有一个子节点40
,用40
替换30
。 - 删除
50
:节点50
有两个子节点,用其右子树的最小值60
替换50
,然后删除60
。
plaintext
删除前:
50
/ \
30 70
/ \ / \
20 40 60 80
删除 20 后:
50
/ \
30 70
\ / \
40 60 80
删除 30 后:
50
/ \
40 70
/ \
60 80
删除 50 后:
60
/ \
40 70
\
80
删除前 50 30 70 20 40 60 80 删除20后 50 30 70 40 60 80 删除30后 50 40 70 60 80 删除50后 60 40 70 80
查找操作
查找操作是搜索二叉搜索树中是否存在某个特定值。根据目标值与当前节点值比较,决定在左子树或右子树中继续查找。以下是查找操作的代码示例:
java
class BinarySearchTree {
TreeNode root;
BinarySearchTree() {
root = null;
}
// 查找指定值是否存在
boolean search(int value) {
return searchRec(root, value);
}
// 递归方式查找值
boolean searchRec(TreeNode root, int value) {
// 基本情况:树为空或值为根节点值
if (root == null || root.value == value) return root != null;
// 值小于根节点值,则在左子树中查找
if (value < root.value)
return searchRec(root.left, value);
// 值大于根节点值,则在右子树中查找
return searchRec(root.right, value);
}
// 插入值到二叉查找树
void insert(int value) {
root = insertRec(root, value);
}
// 递归方式插入值
TreeNode insertRec(TreeNode root, int value) {
// 如果树为空,则创建新节点
if (root == null) {
root = new TreeNode(value);
return root;
}
// 如果值小于根节点,则插入左子树
if (value < root.value)
root.left = insertRec(root.left, value);
// 如果值大于根节点,则插入右子树
else if (value > root.value)
root.right = insertRec(root.right, value);
// 返回(未变更的)节点指针
return root;
}
// 测试查找操作
public static void main(String[] args) {
BinarySearchTree tree = new BinarySearchTree();
tree.insert(50);
tree.insert(30);
tree.insert(20);
tree.insert(40);
tree.insert(70);
tree.insert(60);
tree.insert(80);
// 测试查找操作
System.out.println(tree.search(50)); // 输出:true
System.out.println(tree.search(90)); // 输出:false
}
}
查找操作图解
假设我们在上面的树中查找值 50
和 90
:
- 查找
50
:从根节点开始,50
正是根节点值,查找成功。 - 查找
90
:从根节点50
开始,90
大于50
,查找右子树。然后90
大于70
,查找右子树。接着90
大于80
,发现右子树为空,查找失败。
50 30 70 20 40 60 80 查找50 查找90 null
平衡二叉树
平衡二叉树是一类特殊的二叉搜索树,通过自动调整树的结构,使树的高度保持在较低水平,从而提高查找、插入和删除操作的效率。常见的平衡二叉树有 AVL 树和红黑树。
AVL 树
AVL 树是一种自平衡二叉搜索树,它的每个节点都需要满足以下平衡条件:任意节点的左右子树高度差不超过 1。AVL 树通过旋转操作来保持平衡。
AVL 树的旋转操作
- 右旋(Right Rotation)
- 左旋(Left Rotation)
- 左右旋(Left-Right Rotation)
- 右左旋(Right-Left Rotation)
右旋 左旋 左右旋 右左旋
红黑树
红黑树是一种自平衡二叉搜索树,每个节点都有颜色属性(红色或黑色),并且需要满足以下性质:
- 节点是红色或黑色。
- 根节点是黑色。
- 每个叶节点(NIL 节点)是黑色。
- 如果一个节点是红色,则它的两个子节点都是黑色。
- 从一个节点到该节点的所有后代叶节点的所有路径上,包含相同数目的黑色节点。
红黑树的旋转和颜色翻转
- 左旋(Left Rotation)
- 右旋(Right Rotation)
- 颜色翻转(Color Flip)
左旋 右旋 颜色翻转
红黑树的插入操作
红黑树的插入操作和普通二叉搜索树相同,但插入新节点后需要进行重新平衡操作。以下是红黑树的插入操作代码示例:
java
class RedBlackTreeNode {
int value;
RedBlackTreeNode left, right, parent;
boolean color; // true for Red, false for Black
// 构造函数
RedBlackTreeNode(int item) {
value = item;
left = right = parent = null;
color = true; // new nodes are red by default
}
}
class RedBlackTree {
private RedBlackTreeNode root;
private final RedBlackTreeNode TNULL;
// 初始化
public RedBlackTree() {
TNULL = new RedBlackTreeNode(0);
TNULL.color = false;
TNULL.left = null;
TNULL.right = null;
root = TNULL;
}
// 前序遍历
private void preOrderHelper(RedBlackTreeNode node) {
if (node != TNULL) {
System.out.print(node.value + " ");
preOrderHelper(node.left);
preOrderHelper(node.right);
}
}
// 中序遍历
private void inOrderHelper(RedBlackTreeNode node) {
if (node != TNULL) {
inOrderHelper(node.left);
System.out.print(node.value + " ");
inOrderHelper(node.right);
}
}
// 后序遍历
private void postOrderHelper(RedBlackTreeNode node) {
if (node != TNULL) {
postOrderHelper(node.left);
postOrderHelper(node.right);
System.out.print(node.value + " ");
}
}
// 查找树中的节点
private RedBlackTreeNode searchTreeHelper(RedBlackTreeNode node, int key) {
if (node == TNULL || key == node.value) {
return node;
}
if (key < node.value) {
return searchTreeHelper(node.left, key);
}
return searchTreeHelper(node.right, key);
}
// 平衡插入操作
private void fixInsert(RedBlackTreeNode k) {
RedBlackTreeNode u;
while (k.parent.color == true) {
if (k.parent == k.parent.parent.right) {
u = k.parent.parent.left;
if (u.color == true) {
u.color = false;
k.parent.color = false;
k.parent.parent.color = true;
k = k.parent.parent;
} else {
if (k == k.parent.left) {
k = k.parent;
rightRotate(k);
}
k.parent.color = false;
k.parent.parent.color = true;
leftRotate(k.parent.parent);
}
} else {
u = k.parent.parent.right;
if (u.color == true) {
u.color = false;
k.parent.color = false;
k.parent.parent.color = true;
k = k.parent.parent;
} else {
if (k == k.parent.right) {
k = k.parent;
leftRotate(k);
}
k.parent.color = false;
k.parent.parent.color = true;
rightRotate(k.parent.parent);
}
}
if (k == root) {
break;
}
}
root.color = false;
}
// 左旋
private void leftRotate(RedBlackTreeNode x) {
RedBlackTreeNode y = x.right;
x.right = y.left;
if (y.left != TNULL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
this.root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
// 右旋
private void rightRotate(RedBlackTreeNode x) {
RedBlackTreeNode y = x.left;
x.left = y.right;
if (y.right != TNULL) {
y.right.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
this.root = y;
} else if (x == x.parent.right) {
x.parent.right = y;
} else {
x.parent.left = y;
}
y.right = x;
x.parent = y;
}
// 插入节点
public void insert(int key) {
RedBlackTreeNode node = new RedBlackTreeNode(key);
node.parent = null;
node.value = key;
node.left = TNULL;
node.right = TNULL;
node.color = true;
RedBlackTreeNode y = null;
RedBlackTreeNode x = this.root;
while (x != TNULL) {
y = x;
if (node.value < x.value) {
x = x.left;
} else {
x = x.right;
}
}
node.parent = y;
if (y == null) {
root = node;
} else if (node.value < y.value) {
y.left = node;
} else {
y.right = node;
}
if (node.parent == null) {
node.color = false;
return;
}
if (node.parent.parent == null) {
return;
}
fixInsert(node);
}
// 查找树中的节点
public RedBlackTreeNode searchTree(int k) {
return searchTreeHelper(this.root, k);
}
// 前序遍历
public void preorder() {
preOrderHelper(this.root);
}
// 中序遍历
public void inorder() {
inOrderHelper(this.root);
}
// 后序遍历
public void postorder() {
postOrderHelper(this.root);
}
// 打印树
private void printHelper(RedBlackTreeNode root, String indent, boolean last) {
if (root != TNULL) {
System.out.print(indent);
if (last) {
System.out.print("R----");
indent += " ";
} else {
System.out.print("L----");
indent += "| ";
}
String sColor = root.color ? "RED" : "BLACK";
System.out.println(root.value + "(" + sColor + ")");
printHelper(root.left, indent, false);
printHelper(root.right, indent, true);
}
}
public void printTree() {
printHelper(this.root, "", true);
}
// 测试红黑树
public static void main(String[] args) {
RedBlackTree bst = new RedBlackTree();
bst.insert(55);
bst.insert(40);
bst.insert(65);
bst.insert(60);
bst.insert(75);
bst.insert(57);
bst.printTree();
System.out.println("\n前序遍历:");
bst.preorder();
System.out.println("\n\n中序遍历:");
bst.inorder();
System.out.println("\n\n后序遍历:");
bst.postorder();
}
}
红黑树插入操作图解
假设我们在一棵空的红黑树中插入以下节点:55、40、65、60、75、57,以下是每一步的插入和相应的颜色标注和旋转操作:
- 插入
55
,成为根节点,颜色变为黑色。 - 插入
40
,为55
的左子节点,颜色为红色。 - 插入
65
,为55
的右子节点,颜色为红色。 - 插入
60
,为65
的左子节点,颜色为红色,需要左旋65
。 - 插入
75
,为65
的右子节点,颜色为红色。 - 插入
57
,为60
的左子节点,颜色为红色,需要右旋60
,然后左旋55
。
40 红 55 黑 57 红 60 红 65 红 75 红
结论
通过上述讲解和实例代码,我们详细展示了二叉搜索树的定义、基本操作及平衡二叉树(AVL 树和红黑树)。二叉搜索树在计算机科学中有着广泛的应用,平衡二叉树通过保持树的高度在较低水平,从而提高查找、插入和删除操作的效率。希望这篇博客对您有所帮助!记得关注、点赞和收藏哦,以便随时查阅更多优质内容!
如果您觉得这篇文章对您有帮助,请关注我的CSDN博客,点赞并收藏这篇文章,您的支持是我持续创作的动力!
关键内容总结:
- 二叉搜索树的定义和基本操作
- 二叉搜索树的插入、删除和查找操作
- 平衡二叉树:AVL 树和红黑树
- AVL 树的旋转操作
- 红黑树的性质和操作
推荐阅读:深入探索设计模式专栏 ,详细讲解各种设计模式的应用和优化。点击查看:深入探索设计模式。
特别推荐:设计模式实战专栏 ,深入解析设计模式的实际应用,提升您的编程技巧。点击查看:设计模式实战。
如有任何疑问或建议,欢迎在评论区留言讨论。谢谢阅读!