package com.charles.algo.binarysearchtree;
import com.charles.algo.binarytree.BinaryTreeNode;
/**
* 这个类是二叉搜索树的定义
* @author charles.wang
*
*/
public class BinarySearchTree<V> {
//根节点
private BSTreeNode<V> rootNode;
public BinarySearchTree(){
rootNode = null;
}
/**
* 返回关键字匹配的节点的值
*/
public V search (int key){
//如果根节点为空,那么这个二叉搜索树为空,肯定无匹配值
if(rootNode==null)
return null;
//如果根节点不为空,那么从根开始比较,因为二叉搜索树都是保持了左<根<右的结构,所以很快能找到匹配值
BSTreeNode<V> currentNode = rootNode;
while (currentNode!=null){
//如果当前节点的key刚好和请求的key匹配,那么则返回当前节点的值
if( key==currentNode.getKey())
return currentNode.getValue();
//如果当前的节点的key和请求的key不匹配,那么进行比较
//如果传入的key比当前节点的key小,那么匹配的值(如果有),必定在当前节点的左子搜索树中
//吧待搜节点设为当前节点的左子节点
else if ( key< currentNode.getKey())
currentNode=currentNode.getLeftChild();
//如果传入的key比当前的节点的key大,那么匹配的值(如果有),必定在当前节点的右子搜索树中
//把待搜节点设为当前节点的右子节点
else
currentNode=currentNode.getRightChild();
}
//如果跳出了while循环还没找到匹配的值,说明在这个搜索树中找不到,返回null
return null;
}
/**
* 插入一个节点到二叉搜索树中
* @param key
* @param value
*/
public void insertNode( int key, V value){
//先搜索二叉搜索树是否已经有相同的节点,如果有,则不执行插入动作
if (search(key)!=null)
return;
BSTreeNode<V> newNode= new BSTreeNode<V> (null,null,key,value);
//假设是二叉搜索树还没有任何内容,那么该赋值给根节点
if(rootNode==null){
rootNode = newNode;
}
//假设二叉搜索树已经存在,那么从根开始,找到适合其插入的位置
//如果判断某个位置是否是其可以插入?
//方法是,从根开始标记为当前节点,如果新节点的key比当前节点的key小,那么说明插在当前节点的左子树中
//这时候,如果左节点为空,那么新节点就作为当前节点的左边节点
// 如果左节点不为空,那么比较左节点的key和新节点的key,
// 如果新节点的key大于左节点的key,那么吧新节点插入到左节点和当前节点之间,退出循环
// 如果新节点的key小于左节点的key,那么吧当前节点设为当前节点的左节点。
//右节点处理与上述类似
else{
BSTreeNode<V> currentNode= rootNode;
//比较新节点的key和当前节点的key
while(currentNode!=null){
//插入到左子树中
if(key< currentNode.getKey()){
//当前节点的左节点为空,那么找到了合适位置
if(currentNode.getLeftChild()==null){
currentNode.setLeftChild(newNode);
break;
}
//当前节点的左节点不为空,则比较左节点的key和新节点的key
else{
//如果新节点key大于当前节点的左节点的key,则说明新节点的key刚好位于当前节点的左节点和当前节点之间
//这是一个合适的插入位置
if (key > currentNode.getLeftChild().getKey()){
newNode.setLeftChild(currentNode.getLeftChild());
currentNode.setLeftChild(newNode);
break;
}
//如果新节点key比当前节点左节点还小,那么说明适合插入的位置位于当前节点的左搜索子树中,于是移动当前节点
//到左子节点作为新的比较的基准
else{
currentNode=currentNode.getLeftChild();
}
}
}
//插入到右子树中
else {
//如果当前节点的右节点为空,那么找到了合适的位置
if( currentNode.getRightChild()==null){
currentNode.setRightChild(newNode);
}
//如果当前节点的右节点不为空,那么比较右节点的key和新节点的key
else{
//如果新节点的key小于当前节点的右节点的key,则说明新节点的key刚好位于当前节点和当前节点的右节点之间
//这是一个合适的插入位置
if(key < currentNode.getRightChild().getKey()){
newNode.setRightChild(currentNode.getRightChild());
currentNode.setRightChild(newNode);
break;
}
//如果新节点的key比当前节点的右边节点还大,说明适合插入的位置位于当前节点的右搜索子树中,于是移动当前节点
//到右子节点作为新的比较的基准
else {
currentNode = currentNode.getRightChild();
}
}
}
}
}
}
/**
* 删除指定key的节点
* @param key
*/
public void deleteNode(int key){
//如果二叉搜索树中没有对应key的节点,那么不执行删除操作
if(search(key)==null)
return;
//否则,肯定有key节点,首先定位到这个节点
BSTreeNode<V> currentNode = rootNode;
BSTreeNode<V> currentNodeParentNode= null; //当前节点的父亲节点
while( currentNode.getKey()!=key){
//如果要删除的节点key比当前节点的key小,那么说明要删除的节点在左子树中
if(key<currentNode.getKey()){
currentNodeParentNode=currentNode;
currentNode=currentNode.getLeftChild();
}
else{
currentNodeParentNode=currentNode;
currentNode=currentNode.getRightChild();
}
}
//跳出上述while循环时候,currentNode就指向了即将要被删除的节点,currentNodeParentNode指向了被删除节点的父亲节点
//这时候删除分为若干种情况
//情况1,假设currentNode既有左子节点又有右子节点,那么删除currentNode后有一个洞,
//这个洞的值必须从已有元素中取一个填充,而且这个元素必须比左子树所有节点都大,并且比右子树所有节点都小
//显然这里有2个备选方案:
//一个是左子树的最大节点,它大于所有左子树节点(除去自身),并且小于当前被删节点,也就小于所有右子树节点
//另一个是右子树的最小节点。它小于所有右子树节点(除去自身),并且大于当前被删节点,也就大于所有左子树节点
//如果我们采用了左子树的最大节点,那么这种情况就转为只有左子树的情况了
if( ( currentNode.getLeftChild()!=null) && (currentNode.getRightChild()!=null) ){
//找到左子树的最大节点
BSTreeNode<V> maxNode = currentNode.getLeftChild(); //这变量存放了要找的最大节点
BSTreeNode<V> maxNodeParentNode= currentNode; //这变量存放了要找的最大节点的父节点
while(maxNode.getRightChild()!=null){
//向右移动
maxNodeParentNode=maxNode;
maxNode= maxNode.getRightChild();
}
//吧最大节点插入到currentNode中,从而维持了原有的结构不变
currentNode.setKey(maxNode.getKey());
currentNode.setValue(maxNode.getValue());
//删除左子树的原最大节点
maxNodeParentNode.setRightChild(null);
return ;
}
//情况2,如果当前节点既没有左节点又没有右节点,则直接丢弃
if(( currentNode.getLeftChild()==null) && (currentNode.getRightChild()==null) ){
//如果当前节点为根节点,那么删除根节点
if(currentNode==rootNode){
rootNode=null;
}
//如果当前节点不为根节点,那么必定有父亲节点,这时候只要丢弃当前节点就可以了,判断是判断当前节点是父亲节点的左儿子还是右儿子
if(currentNode==currentNodeParentNode.getLeftChild()){
currentNodeParentNode.setLeftChild(null);
}
else
currentNodeParentNode.setRightChild(null);
return;
}
//情况3,如果当前节点只有左节点或者右节点中的1个,那么吧儿子提升上来
else {
//如果只有左节点
if( currentNode.getLeftChild()!=null){
//如果当前节点为根节点,那么吧左节点提升上来当根节点
if(currentNode==rootNode){
rootNode=currentNode.getLeftChild();
}
//如果当前节点不为根节点,那么必定有父节点,则判断当前节点是父亲节点的左儿子还是右儿子,并且用左节点值取代之
else if(currentNode==currentNodeParentNode.getLeftChild()){
currentNodeParentNode.setLeftChild(currentNode.getLeftChild());
}
else
currentNodeParentNode.setRightChild(currentNode.getLeftChild());
}
//如果只有右节点
else{
//如果当前节点为根节点,那么吧右节点提升上来当根节点
if(currentNode==rootNode){
rootNode=currentNode.getRightChild();
}
//如果当前节点不为根节点,那么必定有父节点,则判断当前节点是父亲节点的左儿子还是右儿子,并且用右节点值取代之
if(currentNode==currentNodeParentNode.getLeftChild()){
currentNodeParentNode.setLeftChild(currentNode.getRightChild());
}
else
currentNodeParentNode.setRightChild(currentNode.getRightChild());
}
}
}
/**
* 因为二叉搜索树中,左<根<右,所以我们可以用中序遍历的方法来打印出所有的节点
*/
public void printAllNodes(){
midOrder(rootNode);
}
/**
* 中序遍历某节点,它会先打印左边节点,再打印当前节点,最后打印出右边节点
* @param args
*/
private void midOrder(BSTreeNode currentNode){
//当前节点如果是null,则不打印出当前节点,并且退出递归
if(currentNode!=null){
midOrder(currentNode.getLeftChild());
System.out.print(currentNode.getValue()+" ");
midOrder(currentNode.getRightChild());
}
}
}
下面我们来验证,我们先构造一组强度彼此不同的人,吧他们放入二叉搜索树,接着我们来演示查找还有删除操作是否每次删除都维持着二叉搜索树中顺序的关系:
public static void main(String[] args){
//构造一颗二叉搜索树,其中key为每个人的能力强度(假设每个人的个人能力都不同) ,value为每个人的名字
//Charles:1000
//Jack: 422
//Penny: 635
//Jude: 95
//John: 23
//Golden 768
//Bull: 79
BinarySearchTree<String> bsTree = new BinarySearchTree<String>();
bsTree.insertNode(1000, "Charles");
bsTree.insertNode(422,"Jack");
bsTree.insertNode(635, "Penny");
bsTree.insertNode(95, "Jude");
bsTree.insertNode(23, "John");
bsTree.insertNode(768, "Golden");
bsTree.insertNode(79, "Bull");
//测试二叉搜索树的搜索
System.out.println("测试在二叉搜索树中搜索.");
int powerKey1 = 422;
String powerValue1 = bsTree.search(powerKey1);
if(powerValue1==null)
System.out.println("找不到匹配强度为:"+powerKey1+"的人.");
else
System.out.println("强度为"+powerKey1+"的人名字叫:"+powerValue1);
int powerKey2 = 420;
String powerValue2 = bsTree.search(powerKey2);
if(powerValue2==null)
System.out.println("找不到匹配强度为:"+powerKey2+"的人.");
else
System.out.println("强度为"+powerKey2+"的人名字叫:"+powerValue2);
System.out.println();
//测试打印出所有二叉搜索树的节点,升序
System.out.println("测试二叉搜索树中各个节点是否有序排列了");
System.out.print("所有人强度按照升序排名为:");
bsTree.printAllNodes();
System.out.println();
//测试二叉搜索树的删除,看每次删除是否还维持二叉搜索树的结构
System.out.println("删除 Jude:");
bsTree.deleteNode(95);
System.out.println("删除后所有人强度升序排名为:");
bsTree.printAllNodes();
System.out.println();
System.out.println("删除 Charles:");
bsTree.deleteNode(1000);
System.out.println("删除后所有人强度升序排名为:");
bsTree.printAllNodes();
}