二叉查找树简介
二叉查找树(Binary Search Tree),又被称为二叉搜索树。 它是特殊的二叉树:对于二叉树,假设x为二叉树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。那么,这棵树就是二叉查找树。如下图所示:
在二叉查找树中: (1) 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2) 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3) 任意节点的左、右子树也分别为二叉查找树。 (4) 没有键值相等的节点(no duplicate nodes)。
二叉查找树的实现
1.二叉查找树节点实现
代码语言:javascript复制public class BinaryNode<T> {
private T element;
private BinaryNode<T> left;
private BinaryNode<T> right;
public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {
this.element = element;
this.left = left;
this.right = right;
}
public BinaryNode(T element) {
this(element, null, null);
}
}
(1) element–当前节点的值 (2) right – 它指向当前节点的右孩子。 (3) parent – 它指向当前节点的父结点。
2.contains方法的实现
如果在树T中存在含有项X的节点,那么contains方法返回true,否则返回false.如果树T是空集,则返回false.
代码语言:javascript复制public boolean contains(T t, BinaryNode<T> node) {
if (t == null) {
return false;
}
int campareResult = t.compareTo(node.element);
if (campareResult < 0) {
return contains(t, node.left);
} else if (campareResult > 0) {
return contains(t, node.right);
} else {
return true;
}
}
从根节点开始查找该值,如果根节点匹配,则直接返回true,否则和根节点的值进行比较,然后递归遍历左子树或右子树进行查找。
3.findMin方法和findMax方法
代码语言:javascript复制 public T findMax() {
if (isEmpty()) {
throw new IndexOutOfBoundsException();
}
return findMax(mRoot).element;
}
private BinaryNode<T> findMax(BinaryNode<T> node) {
if (node != null) {
while (node.right != null) {
node = node.right;
}
}
return node;
}
对于findMax方法,如果存在右子树,则递归遍历,终止点就是最大的元素。否则,根节点就是最大的元素。
代码语言:javascript复制 public T findMin(){
if (isEmpty()) {
throw new IndexOutOfBoundsException();
}
return findMin(mRoot).element;
}
private BinaryNode<T> findMin(BinaryNode<T> node) {
if (node != null) {
while (node.left != null) {
node = node.left;
}
}
return node;
}
对于findMin方法,如果存在左子树,则递归遍历,终止点就是最小的元素。否则,根节点就是最小的元素。
4.insert方法 将一个新的元素X插入到树T中,可以先通过contains方法去查找该元素是否存在,如果存在,则什么都不做,否则将X插入到遍历路径上的最后一点。
代码语言:javascript复制public void insert(T t) {
mRoot = insert(t, mRoot);
}
public BinaryNode<T> insert(T t, BinaryNode<T> node) {
if (node == null) {
return new BinaryNode<T>(t, null, null);
}
int compareResult = t.compareTo(node.element);
if (compareResult > 0) {
node.right = insert(t, node.right);
} else if (compareResult < 0) {
node.left = insert(t, node.left);
}
return node;
}
5.remove方法
代码语言:javascript复制public void remove(T t) {
mRoot = remove(t, mRoot);
}
public BinaryNode<T> remove(T t, BinaryNode<T> node) {
if (node == null) {
return node;
}
int compareResult = t.compareTo(node.element);
if (compareResult > 0) {
node.right = remove(t, node.right);
} else if (compareResult < 0) {
node.left = remove(t, node.left);
} else if (node.left != null && node.right != null) {
node.element = findMin(node.right).element;
node.right = remove(node.element, node.right);
} else {
node = (node.left != null) ? node.left : node.right;
}
return node;
}
对于删除,最复杂的是该将被删除的节点A具有两个儿子的情况,一般是该节点的右子树最小的节点B代替节点A,然后在B原来的位置删除掉节点B。
上面的代码能够完成删除工作,但效率并不高,因为它沿着树进行两趟搜索来查找和删除右子树中最小的节点。
如果删除的次数不多,通常使用懒惰删除:当一个元素要被删除时,它仍在树中,而只是标记为删除。
特别是删除算法会使得左子树比右子树深,因为我们总是用右子树的一个来代替删除的节点。会造成二叉查找树,严重的不平衡。
5.二叉查找树完整代码
代码语言:javascript复制public class BinarySearchTree<T extends Comparable<?
super T>> {
private BinaryNode<T> mRoot;
public void makeEmpty() {
mRoot = null;
}
public boolean isEmpty() {
return mRoot == null;
}
public boolean contains(T t) {
return contains(t, mRoot);
}
public boolean contains(T t, BinaryNode<T> node) {
if (t == null) {
return false;
}
int campareResult = t.compareTo(node.element);
if (campareResult < 0) {
return contains(t, node.left);
} else if (campareResult > 0) {
return contains(t, node.right);
} else {
return true;
}
}
public T findMax() {
if (isEmpty()) {
throw new IndexOutOfBoundsException();
}
return findMax(mRoot).element;
}
private BinaryNode<T> findMax(BinaryNode<T> node) {
if (node != null) {
while (node.right != null) {
node = node.right;
}
}
return node;
}
public T findMin() {
if (isEmpty()) {
throw new IndexOutOfBoundsException();
}
return findMin(mRoot).element;
}
private BinaryNode<T> findMin(BinaryNode<T> node) {
if (node != null) {
while (node.left != null) {
node = node.left;
}
}
return node;
}
public void insert(T t) {
mRoot = insert(t, mRoot);
}
public BinaryNode<T> insert(T t, BinaryNode<T> node) {
if (node == null) {
return new BinaryNode<T>(t, null, null);
}
int compareResult = t.compareTo(node.element);
if (compareResult > 0) {
node.right = insert(t, node.right);
} else if (compareResult < 0) {
node.left = insert(t, node.left);
}
return node;
}
public void remove(T t) {
mRoot = remove(t, mRoot);
}
public BinaryNode<T> remove(T t, BinaryNode<T> node) {
if (node == null) {
return node;
}
int compareResult = t.compareTo(node.element);
if (compareResult > 0) {
node.right = remove(t, node.right);
} else if (compareResult < 0) {
node.left = remove(t, node.left);
} else if (node.left != null && node.right != null) {
node.element = findMin(node.right).element;
node.right = remove(node.element, node.right);
} else {
node = (node.left != null) ? node.left : node.right;
}
return node;
}
private static class BinaryNode<T> {
private T element;
private BinaryNode<T> left;// 左孩子
private BinaryNode<T> right;// 右孩子
public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {
this.element = element;
this.left = left;
this.right = right;
}
public BinaryNode(T element) {
this(element, null, null);
}
}
}
二叉树的遍历
1.前序遍历 若二叉树非空,则执行以下操作: (1) 访问根结点; (2) 先序遍历左子树; (3) 先序遍历右子树。
前序遍历代码
代码语言:javascript复制 //前序遍历
private void preOrder(BinaryNode<T> tree) {
if(tree != null) {
System.out.print(tree.element " ");
preOrder(tree.left);
preOrder(tree.right);
}
}
public void preOrder() {
preOrder(mRoot);
}
2.中序遍历 若二叉树非空,则执行以下操作: (1) 中序遍历左子树; (2) 访问根结点; (3) 中序遍历右子树。
中序遍历代码
代码语言:javascript复制private void inOrder(BinaryNode<T> tree) {
if(tree != null) {
inOrder(tree.left);
System.out.print(tree.element " ");
inOrder(tree.right);
}
}
public void inOrder() {
inOrder(mRoot);
}
3.后序遍历 若二叉树非空,则执行以下操作: (1) 后序遍历左子树; (2) 后序遍历右子树; (3) 访问根结点。
后序遍历代码
代码语言:javascript复制 private void postOrder(BinaryNode<T> tree) {
if(tree != null)
{
postOrder(tree.left);
postOrder(tree.right);
System.out.print(tree.element " ");
}
}
public void postOrder() {
postOrder(mRoot);
}