二叉查找树 BinarySearchTree < T extends Comparable <? super T> >
想到哪说哪
本文是对《数据结构与算法分析(Java语言描述)》的学习,关键代码和思想均源于教材,后面代码是自己的整理,和对教材中出于精简考虑没给出的代码的补全。
通配符<? super T>,表示T或T的父类,extends反之;
T extends Comparable <? super T>,这里的接口Comparable <? super T>用来指代实现了Comparable <? super T>接口的泛型类(泛型是T或T的父类),T继承该类表示T具有可比较的性质。
注意,这里的继承是广义的,如果是继承的该类是T的父类(父类实现了Comparable<>接口),这就是一般意义上的继承;另一方面后面接口可以指代T本身,也即T实现了Comparable<>接口,extends就只是一种记法(继承了自己),即表示T具有Comparable<>的性质。
以上面这个可比较的T为泛型的 BinarySearchTree,首先有个私有的static class BinaryNode <T>,它和链表其实是类似的。然后因为它是private的,所以它的成员对外部类以外是不可见的,而对外部类又必然可见,所以它的成员的访问属性可以是任意的。
子类可以通过super关键字调用父类的构造函数。一个类的构造函数可以通过this关键字来调用自己的其他构造函数。this 和super在一个构造函数中只能有一个,且都必须是构造函数当中的第一行。
private static class BinaryNode<T> {
// Because of the class's private attribute, the members' attributes here don't matter.
T element;
public BinaryNode<T> left;
private BinaryNode<T> right;
public BinaryNode(T paraElement, BinaryNode<T> paraLeft, BinaryNode<T> paraRight) {
element = paraElement;
left = paraLeft;
right = paraRight;
}// of the first Constructor
public BinaryNode(T paraElement) {
this(paraElement, null, null);
}// of the second Constructor
}// of class BinaryNode<T>
二叉查找树是一种结构,需要实现
void makeEmpty();
boolean isEmpty();
boolean contains(T x);
T findMin();
T findMax();
void insert(T x);
void remove(T x);
void printTree();
contains(T, x)是非常巧妙的,它通过一个递归的private重载函数来实现,具体精妙之处见代码。
public boolean contains(T x) {
return contains(x, root);
}// of contains(T)
/**
**********************************
* Internal method to find an item in a subtree.
*
* @param x the item to search for.
* @param t the node that roots the subtree. // root在这里是“是……的根(节点)”的意思
* @return true if the item is found; false otherwise.
**********************************
*/
private boolean contains(T x, BinaryNode<T> t) {
if (t == null) {
return false;
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
return contains(x, t.left);
} else if (compareResult > 0) {
return contains(x, t.right);
} else {
return true;
} // of if-elseIf-else
}// of contains(T, BinaryNode<T>)
findMin()也是通过一个private方法来实现,可以看到我们将内部数据保护得非常好,就所有的公开方法都没有用到任何节点,跟节点有关的成员和方法全都是private的。在具体的findMin(BinaryNode)中,给出了2种实现方法,一种是递归的实现,这种方法非常好理解,另一种是一个while循环实现,理论上如果操作正确,那么直接实现总是比递归来得快,即使设计极佳的递归,直接实现也可以做到比递归省空间,但是在一些稍复杂的情况(还不需要特别复杂),直接实现就会出现代码阅读性很低,理解复杂、维护和修改困难的情况。
举个例子:后面remove方法中的两行代码t.element = findMin(t.right).element;t.right = remove(t.element,t.right);,实际上进行了2次查找,而第2次查找其实是没必要的,理论上完全可以在第一次找到点后直接进行后续链的修改;但如果把这两句话写成一个新的函数,虽然省了一点点计算量,但复杂到写完几十行根本不想再看和维护,原因类似于涉及到对父节点和子节点的单链(而不是双链)的处理。
善用递归,人生苦短。
public T findMin() {
if (isEmpty()) {
System.out.println("No minimum element, it's an empty tree.");
return null;
// throw new BufferUnderflowException();
} // of if
return findMin(root).element;
}// of findMin()
/**
**********************************
* Internal method to find the smallest item in a subtree.
*
* @param t the node that roots the subtree.
* @return the node containing the smallest item.
**********************************
*/
private BinaryNode<T> findMin(BinaryNode<T> t) {
// if (t==null) {
// return null;
// }else if(t.left == null){
// return t;
// } // of if-elseIf
//
// return findMin(t.left);
if (t != null) {
while (t.left != null) {
t = t.left;
} // of while
} // of if
return t;
}// of findMin(BinaryNode<T>)
insert(T x)方法更为巧妙。虽然和前面一样,是通过一个private的重载函数来实现,但是注意一个细节,这里的public void insert(T x)是借助 BinaryNode<T> insert(T x, BinaryNode t)来实现的,这里的重载函数实质上并没有完成insert过程,还少链接一个链,private的insert(T x, BinaryNode t)方法要返回以t为根的子树在插入后的新root节点,这个节点绝大部分时候和插入前一样,只有递归到需插入的节点时,此时返回的是一个全新的新生成的需插入的节点。可以看到递归过程会多生成和返回一个节点,因此public的insert(T x)方法需要建立一个链,通过root = insert(x, root);完成,如果t是null,那么这个链就是建立个根节点,如果t没有子树,那么这里的链才是完成insert的链,如果有子树,那么实际上insert的链在private方法的递归中连接,但树顶的节点还是要重新链接上。
public void insert(T x) {
root = insert(x, root);
}// of insert
/**
**********************************
* Internal method to insert into a subtree.
*
* @param x the item to insert.
* @param t the node that roots the subtree.
* @return the new root of the subtree after being inserted.
**********************************
*/
private BinaryNode<T> insert(T x, BinaryNode<T> t) {
if (t == null) {
return new BinaryNode<>(x);
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
} else {
; // Duplicate; here we do nothing.
} // of if-elseIf-else
return t;
}// of insert(T, BinaryNode<T>)
remove(T x)方法就更更巧妙了。同样用到递归;根据实际情况把找到待删除的节点后的问题分为有两个非空子树和至少一个空子树的情况。有两个非空子树的情况,就是在右子树中找值最小的节点(或在左子树中找最大)来取代要删除的该根节点,加粗字体说明了可取代的原因。在代码的实现中,取代只需要令根节点的element成员等于那个最值即可,然后再在右子树中删除找到的那个节点,注意,此时就转化到“至少一个空子树”的情况了。在删除根节点而该节点至少有一个空子树时,这是回顾一下前面指出的差异,private的remove方法返回的时remove后的新的根节点(并没有考虑根节点的链!),若根的左儿子非空,则删除原根后新根为左儿子;其他还有右儿子非空和都是空的2种情况,要么返回右儿子,要么返回null,这两种可合并为都返回右儿子(都null则右儿子也null),然后采用了一个三目运算符" ? : ",注意此时的t = (t.left != null) ? t.left : t.right;只是说新根是什么,没有连接链,链是在递归中,或者void remove(T x)的root = remove(x, root);中连接上的。
public void remove(T x) {
root = remove(x, root);
}// of remove(T)
/**
**********************************
* Internal method to remove from a subtree.
*
* @param x the item to remove.
* @param t the node that roots the subtree.
* @return the new root of the subtree after being removed.
**********************************
*/
private BinaryNode<T> remove(T x, BinaryNode<T> t) {
if (t == null) {
return t; // item not found; do nothing
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) { // two children
t.element = findMin(t.right).element;
t.right = remove(t.element,t.right);
} else { // one or zero child
t = (t.left != null) ? t.left : t.right;
} // of if-elseIf-else
return t;
}// of remove(T, BinaryNode<T>)
打印输出元素非常简单,pre、in、post order三种遍历只需调整语句顺序即可。
完整代码和测试语句如下:
package myTree;
import java.nio.BufferUnderflowException;
/**
* @author CatInCoffee.
*/
public class BinarySearchTree<T extends Comparable<? super T>> {
private static class BinaryNode<T> {
// As class's attribute is private, the members' attributes here don't matter.
T element;
public BinaryNode<T> left;
private BinaryNode<T> right;
public BinaryNode(T paraElement) {
this(paraElement, null, null);
}// of the first Constructor
public BinaryNode(T paraElement, BinaryNode<T> paraLeft, BinaryNode<T> paraRight) {
element = paraElement;
left = paraLeft;
right = paraRight;
}// of the second Constructor
}// of class BinaryNode<T>
private BinaryNode<T> root;
public BinarySearchTree() {
root = null;
}// of the first Constructor
public void makeEmpty() {
root = null;
}// of makeEmpty
public boolean isEmpty() {
return root == null;
}// of isEmpty
public boolean contains(T x) {
return contains(x, root);
}// of contains(T)
/**
**********************************
* Internal method to find an item in a subtree.
*
* @param x the item to search for.
* @param t the node that roots the subtree. // root means "be the root of..."
* @return true if the item is found; false otherwise.
**********************************
*/
private boolean contains(T x, BinaryNode<T> t) {
if (t == null) {
return false;
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
return contains(x, t.left);
} else if (compareResult > 0) {
return contains(x, t.right);
} else {
return true;
} // of if-elseIf-else
}// of contains(T, BinaryNode<T>)
public T findMin() {
if (isEmpty()) {
System.out.println("No minimum element, it's an empty tree.");
return null;
// throw new BufferUnderflowException();
} // of if
return findMin(root).element;
}// of findMin()
/**
**********************************
* Internal method to find the smallest item in a subtree.
*
* @param t the node that roots the subtree.
* @return the node containing the smallest item.
**********************************
*/
private BinaryNode<T> findMin(BinaryNode<T> t) {
// if (t==null) {
// return null;
// }else if(t.left == null){
// return t;
// } // of if-elseIf
//
// return findMin(t.left);
if (t != null) {
while (t.left != null) {
t = t.left;
} // of while
} // of if
return t;
}// of findMin(BinaryNode<T>)
public T findMax() {
if (isEmpty()) {
System.out.println("No maximum element, it's an empty tree.");
return null;
// throw new BufferUnderflowException();
} // of if
return findMax(root).element;
}// of findMax()
/**
**********************************
* Internal method to find the largest item in a subtree.
*
* @param t the node that roots the subtree.
* @return the node containing the largest item.
**********************************
*/
private BinaryNode<T> findMax(BinaryNode<T> t) {
if (t != null) {
while (t.right != null) {
t = t.right;
} // of while
} // of if
return t;
}// of findMax(BinaryNode<T>)
public void insert(T x) {
root = insert(x, root);
}// of insert
/**
**********************************
* Internal method to insert into a subtree. Actually the insert operation
* finishes when "root = insert(x,root)", which means that the private method
* doesn't finish the whole insert operation.
*
* @param x the item to insert.
* @param t the node that roots the subtree.
* @return the new root of the subtree after being inserted.
**********************************
*/
private BinaryNode<T> insert(T x, BinaryNode<T> t) {
if (t == null) {
return new BinaryNode<>(x);
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
} else {
; // Duplicate; here we do nothing.
} // of if-elseIf-else
return t;
}// of insert(T, BinaryNode<T>)
public void remove(T x) {
root = remove(x, root);
}// of remove(T)
/**
**********************************
* Internal method to remove from a subtree. The remove operation finishes when
* "root = remove(x,root)", which means that the private method doesn't finish
* the whole remove operation.
*
* @param x the item to remove.
* @param t the node that roots the subtree.
* @return the new root of the subtree after being removed.
**********************************
*/
private BinaryNode<T> remove(T x, BinaryNode<T> t) {
if (t == null) {
return t; // item not found; do nothing
} // of if
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) { // two children
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
} else { // one or zero child
t = (t.left != null) ? t.left : t.right;
} // of if-elseIf-else
return t;
}// of remove(T, BinaryNode<T>)
public void printTree() {
if (root == null) {
System.out.println("An empty tree.");
} // of if
printTree(root);
}// of printTree()
private void printTree(BinaryNode<T> t) { // Printing empty tree does nothing.
if (t != null) {
// pre-order traversal
// System.out.print(t.element + " ");
// printTree(t.left);
// printTree(t.right);
// in-order traversal
printTree(t.left);
System.out.print(t.element + " ");
printTree(t.right);
// post-order traversal
// printTree(t.left);
// printTree(t.right);
// System.out.print(t.element + " ");
} // of if
}// of printTree(BinaryNode<T>)
public static void main(String[] args) {
BinarySearchTree<Character> tempTree = new BinarySearchTree<>();
tempTree.insert('d');tempTree.insert('b');tempTree.insert('f');
System.out.print("Inserted, the tree is:");tempTree.printTree();System.out.println();
tempTree.insert('a');tempTree.insert('c');tempTree.insert('e'); tempTree.insert('g');
System.out.print("Inserted, the tree is:");tempTree.printTree();System.out.println();
System.out.println("Is tempTree empty? " + tempTree.isEmpty());
System.out.println("Does tempTree contain 'g'? " + tempTree.contains('g'));
tempTree.remove('a');
System.out.print("Removing 'a', the tree is:");tempTree.printTree();System.out.println();
tempTree.remove('d');
System.out.print("Removing 'd', the tree is:");tempTree.printTree();System.out.println();
tempTree.remove('f');
System.out.print("Removing 'f', the tree is:");tempTree.printTree();System.out.println();
System.out.println("Does tempTree contain 'd'? " + tempTree.contains('d'));
System.out.println("The minimum element is: " + tempTree.findMin());
System.out.println("The maximum element is: " + tempTree.findMax());
tempTree.makeEmpty();
System.out.print("After makeEmpty, the tree is: ");tempTree.printTree();
}// of main
}// of class BinarySearchTree<T extends Comparable<? super T>>
结果:
Inserted, the tree is:b d f
Inserted, the tree is:a b c d e f g
Is tempTree empty? false
Does tempTree contain 'g'? true
Removing 'a', the tree is:b c d e f g
Removing 'd', the tree is:b c e f g
Removing 'f', the tree is:b c e g
Does tempTree contain 'd'? false
The minimum element is: b
The maximum element is: g
After makeEmpty, the tree is: An empty tree.