定义
二叉查找树(Binary Search Tree)又称二叉排序树、二叉搜索树。二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。二叉查找树要求,在树中的任意一个节点都要满足,其左子树中每个节点的值,都要小于这个节点的值,而右子树每个节点的值都大于这个节点的值。
这两个都是二叉查找树。
查找
在二叉查找树中查找一个节点,我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就左子树中递归查找;如果要查找的数据比根节点的值大,那就右子树中递归查找。
查找的代码实现
public class BinarySearchTree {
private Node root;
public Node find(int data) {
Node temp = root;
while (temp != null) {
if (data == temp.data) {
return temp;
} else if(data > temp.data) {
temp = temp.rchild;
} else {
temp = temp.lchild;
}
}
return null;
}
}
public class Node {
int data;
Node lchild;
Node rchild;
public Node(int data) {
this.data = data;
}
}
插入
插入的操作类似于查找的操作。从根节点开始,依次比较要插入的数据和节点的关系。
如果要插入的数据比节点的数据大,并且节点的右子树为空,就将数据直接插入到右子节点的位置,如果右子树不为空,那就继续遍历右子树;如果要插入的数据比节点的数据小,并且节点的左子树为空,就将数据直接插入到左子节点的位置,如果左子树不为空,那就继续遍历左子树。
插入的代码实现
public void insert(int data) {
if (root == null) {
root = new Node(data);
return;
}
Node temp = root;
while (true) {
if (data > temp.data) {
if (temp.rchild == null) {
temp.rchild = new Node(data);
return;
}
temp = temp.rchild;
} else {
if (temp.lchild == null) {
temp.lchild = new Node(data);
return;
}
temp = temp.lchild;
}
}
}
删除
二叉查找树的查找和插入的操作比较简单,但是删除的操作较为复杂,分为三种情况。
第一种情况,要删除的节点是叶子节点。此时只需要直接将父节点中指向要删除节点的指针置为null即可。如下图中删除28。
第二种情况,要删除的节点有只有一个子节点(左子节点或右子节点),我们只需要更新父节点中的指针,让它指向要删除节点的字节点就可以了。如下图中的34。
第三种情况,要删除的节点有两个子节点。这种情况下,我们需要把要删除节点的右子树中最小的节点替换要删除节点。如下图中的15。
删除完成后
代码实现
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
if (root.left == null) return root.right;
if (root.right == null) return root.left;
TreeNode minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, minNode.val);
} else if (root.val > key) {
root.left = deleteNode(root.left, key);
} else {
root.right = deleteNode(root.right, key);
}
return root;
}
private TreeNode getMin(TreeNode root) {
while (root.left != null) {
root = root.left;
}
return root;
}
非递归实现:
public void delete(int data) {
Node temp = root; // temp指向要删除的节点
Node ftemp = null; // ftemp指向要删除节点的父节点
while (temp != null && temp.data != data) {
ftemp = temp;
if (data > temp.data) {
temp = temp.rchild;
} else {
temp = temp.lchild;
}
}
if (temp == null) return; // 没有找到对应的节点
// 若找到,temp就是要删除的节点
// 要删除的节点有两个子节点
if (temp.lchild != null && temp.rchild != null) {
Node minTemp = temp.rchild; // 存储右子树的最小节点
Node fminTemp = temp; // minTemp的父节点
// 找到右子树的最小节点
while (minTemp.lchild != null) {
fminTemp = minTemp;
minTemp = minTemp.lchild;
}
temp.data = minTemp.data; // 将最小节点的值替换到temp中
temp = minTemp; // 变成删除叶子节点
ftemp = fminTemp;
}
// 删除节点是叶子节点或者仅有一个节点
Node child; // temp的子节点
if (temp.lchild != null) {
child = temp.lchild;
} else if (temp.rchild != null) {
child = temp.rchild;
} else {
child = null;
}
if (ftemp == null) {
// 删除的是根节点
root = child;
} else if (ftemp.lchild == temp) {
ftemp.lchild = child;
} else {
ftemp.rchild = child;
}
}
支持重复数据的二叉查找树
前面对二叉查找树的操作,针对的都是不存在重复数据的情况。如果需要存储重复数据,在插入数据时,如果碰到一个节点的值和要插入的值相等,我们把这个要插入的数据放在这个节点的右子树。也就是说,把这个新插入的数据当作大于这个节点的值来处理。
当要查找数据的时候,遇到值相同的节点,我们并不停止查找操作,而是继续在右子树中查找,直到遇到叶子节点,才停止。这样就可以把所有符合要求的节点都查出来。
删除节点时,也需要先查找到每个要删除的节点,然后按之前的删除操作依次删除。
查找最大最小节点
public Node findMin() {
if (root == null) return null;
Node temp = root;
while (temp.lchild != null) {
temp = temp.lchild;
}
return temp;
}
public Node findMax() {
if (root == null) return null;
Node temp = root;
while (temp.rchild != null) {
temp = temp.rchild;
}
return temp;
}
二叉树的遍历
前序、中序、后序遍历(递归)
中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是O(n),非常高效
// 前序遍历
public void preOrder(Node node) {
if (node != null) {
System.out.println(node.data);
preOrder(node.lchild);
preOrder(node.rchild);
}
}
// 中序遍历
public void inOrder(Node node) {
if (node != null) {
inOrder(node.lchild);
System.out.println(node.data);
inOrder(node.rchild);
}
}
// 后序遍历
public void postOrder(Node node) {
if (node != null) {
postOrder(node.lchild);
postOrder(node.rchild);
System.out.println(node.data);
}
}
前序、中序、后序遍历(非递归)
前序
// 前序遍历
public void preOrderTraversal(Node node) {
if (node == null) return;
Stack<Node> stack = new Stack<>();
stack.push(node);
while (!stack.isEmpty()) {
node = stack.pop();
if (node.rchild != null) {
stack.push(node.rchild);
}
if (node.lchild != null) {
stack.push(node.lchild);
}
System.out.println(node.data);
}
}
中序
// 中序遍历
public void inOrderTraversal(Node node) {
Stack<Node> stack = new Stack<>();
while (node != null || !stack.isEmpty()) {
if (node != null) {
stack.push(node);
node = node.lchild;
} else {
node = stack.pop();
System.out.println(node.data);
node = node.rchild;
}
}
}
后序
// 后序遍历
public void postOrderTraversal(Node node) {
if (node == null) return;
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
stack1.push(node);
while (!stack1.isEmpty()) {
node = stack1.pop();
stack2.push(node);
if (node.lchild != null) {
stack1.push(node.lchild);
}
if (node.rchild != null) {
stack1.push(node.rchild);
}
}
while (!stack2.isEmpty()) {
System.out.println(stack2.pop().data);
}
}
层序遍历
// 层序遍历
public void levelOrder(Node node) {
if (node == null) return;
Queue<Node> queue = new LinkedList<>();
queue.offer(node);
while (!queue.isEmpty()) {
node = queue.poll();
System.out.println(node.data);
if (node.lchild != null) {
queue.offer(node.lchild);
}
if (node.rchild != null) {
queue.offer(node.rchild);
}
}
}