java完成二叉搜索树功用

java完成二叉搜索树功用 概念 二叉搜索树也成二叉排序树,它有这么一个特性,某个节点,若其有两个子节点,则一定满足,左子节点值一定小于该节点值,右子节点值一定大于该节点值,关于非根本类型的比拟,能够完成Comparator接口,在本文中为了便当,采用了int类型数据停止操作。
要想完成一颗二叉树,肯定得从它的增加说起,只要把树构建出来了,才干运用其他操作。
二叉搜索树构建
谈起二叉树的增加,肯定先得构建一个表示节点的类,该节点的类,有这么几个属性,节点的值,节点的父节点、左节点、右节点这四个属性,代码如下

static class Node{ Node parent; Node leftChild; Node rightChild; int val; public Node(Node parent, Node leftChild, Node rightChild,int val) { super(); this.parent = parent; this.leftChild = leftChild; this.rightChild = rightChild; this.val = val; } public Node(int val){ this(null,null,null,val); } public Node(Node node,int val){ this(node,null,null,val); } }

复制代码
这里采用的是内部类的写法,构建完节点值后,再对整棵树去构建,一棵树,先得有根节点,再能延伸到余下子节点,那在这棵树里,也有一些属性,比方根本的根节点root,树中元素大小size,这两个属性,假如采用了泛型,可能还得增加Comparator属性,或提供其一个默许完成。详细代码如下

public class SearchBinaryTree { private Node root; private int size; public SearchBinaryTree() { super(); } }

复制代码
增加 【java完成二叉搜索树功用】当要停止添加元素的时分,得思索根节点的初始化,普通状况有两种、当该类的结构函数一初始化就对根节点root停止初始化,第二种、在停止第一次添加元素的时分,对根节点停止添加。理论上两个都能够行得通,但通常采用的是第二种懒加载方式。
在停止添加元素的时分,有这样几种状况需求思索
一、添加时判别root能否初始化,若没初始化,则初始化,将该值赋给根节点,size加一。
二、由于二叉树搜索树满足根节点值大于左节点,小于右节点,需求将插入的值,先同根节点比拟,若大,则往右子树中停止查找,若小,则往左子树中停止查找。直到某个子节点。
这里的插入完成,能够采用两种,一、递归、二、迭代(即经过while循环形式)。
递归版本插入
public boolean add(int val){ if(root == null){ root = new Node(val); size++; return true; } Node node = getAdapterNode(root, val); Node newNode = new Node(val); if(node.val > val){ node.leftChild = newNode; newNode.parent = node; }else if(node.val < val){ node.rightChild = newNode; newNode.parent = node; }else{ // 暂不做处置 } size++; 19return true; } /** * 获取要插入的节点的父节点,该父节点满足以下几种状态之一 * 1、父节点为子节点 * 2、插入节点值比父节点小,但父节点没有左子节点 * 3、插入节点值比父节点大,但父节点没有右子节点 * 4、插入节点值和父节点相等。 * 5、父节点为空 * 假如满足以上5种状况之一,则递归中止。 * @param node * @param val * @return */ private Node getAdapterNode(Node node,int val){ if(node == null){ return node; } // 往左子树中插入,但没左子树,则返回 if(node.val > val && node.leftChild == null){ return node; } // 往右子树中插入,但没右子树,也返回 if(node.val < val && node.rightChild == null){ return node; } // 该节点是叶子节点,则返回 if(node.leftChild == null && node.rightChild == null){ return node; } if(node.val > val && node.leftChild != null){ return getAdaptarNode(node.leftChild, val); }else if(node.val < val && node.rightChild != null){ return getAdaptarNode(node.rightChild, val); }else{ return node; } }

复制代码
运用递归,先找到递归的完毕点,再去把整个问题化为子问题,在上述代码里,逻辑大致是这样的,先判别根节点有没有初始化,没初始化则初始化,完成后返回,之后经过一个函数去获取适配的节点。之后停止插入值。
迭代版本
public boolean put(int val){ return putVal(root,val); } private boolean putVal(Node node,int val){ if(node == null){// 初始化根节点 node = new Node(val); root = node; size++; return true; } Node temp = node; Node p; int t; /** * 经过do while循环迭代获取最佳节点, */ do{ p = temp; t = temp.val-val; if(t > 0){ temp = temp.leftChild; }else if(t < 0){ temp = temp.rightChild; }else{ temp.val = val; return false; } }while(temp != null); Node newNode = new Node(p, val); if(t > 0){ p.leftChild = newNode; }else if(t < 0){ p.rightChild = newNode; } size++; return true; }

复制代码
原理其实和递归一样,都是获取最佳节点,在该节点上停止操作。
论起性能,肯定迭代版本最佳,所以普通状况下,都是选择迭代版本停止操作数据。
删除 能够说在二叉搜索树的操作中,删除是最复杂的,要思索的状况也相对多,在常规思绪中,删除二叉搜索树的某一个节点,肯定会想到以下四种状况,
1、要删除的节点没有左右子节点,如上图的D、E、G节点
2、要删除的节点只要左子节点,如B节点
3、要删除的节点只要右子节点,如F节点
4、要删除的节点既有左子节点,又有右子节点,如 A、C节点
关于前面三种状况,能够说是比拟简单,第四种复杂了。下面先来剖析第一种
若是这种状况,比方 删除D节点,则能够将B节点的左子节点设置为null,若删除G节点,则可将F节点的右子节点设置为null。详细要设置哪一边,看删除的节点位于哪一边。
第二种,删除B节点,则只需将A节点的左节点设置成D节点,将D节点的父节点设置成A即可。详细设置哪一边,也是看删除的节点位于父节点的哪一边。
第三种,同第二种。
第四种,也就是之前说的有点复杂,比方要删除C节点,将F节点的父节点设置成A节点,F节点左节点设置成E节点,将A的右节点设置成F,E的父节点设置F节点(也就是将F节点交换C节点),还有一种,直接将E节点交换C节点。那采用哪一种呢,假如删除节点为根节点,又该怎样删除?
关于第四种状况,能够这样想,找到C或者A节点的后继节点,删除后继节点,且将后继节点的值设置为C或A节点的值。先来补充下后继节点的概念。
一个节点在整棵树中的后继节点必满足,大于该节点值得一切节点汇合中值最小的那个节点,即为后继节点,当然,也有可能不存在后继节点。
但是关于第四种状况,后继节点一定存在,且一定在其右子树中,而且还满足,只要一个子节点或者没有子节点两者状况之一。详细缘由能够这样想,由于后继节点要比C节点大,又由于C节点左右子节一定存在,所以一定存在右子树中的左子节点中。就比方C的后继节点是F,A的后继节点是E。
有了以上剖析,那么完成也比拟简单了,代码如下
public boolean delete(int val){ Node node = getNode(val); if(node == null){ return false; } Node parent = node.parent; Node leftChild = node.leftChild; Node rightChild = node.rightChild; //以下一切父节点为空的状况,则标明删除的节点是根节点 if(leftChild == null && rightChild == null){//没有子节点 if(parent != null){ if(parent.leftChild == node){ parent.leftChild = null; }else if(parent.rightChild == node){ parent.rightChild = null; } }else{//不存在父节点,则标明删除节点为根节点 root = null; } node = null; return true; }else if(leftChild == null && rightChild != null){// 只要右节点 if(parent != null && parent.val > val){// 存在父节点,且node位置为父节点的左边 parent.leftChild = rightChild; }else if(parent != null && parent.val < val){// 存在父节点,且node位置为父节点的右边 parent.rightChild = rightChild; }else{ root = rightChild; } node = null; return true; }else if(leftChild != null && rightChild == null){// 只要左节点 if(parent != null && parent.val > val){// 存在父节点,且node位置为父节点的左边 parent.leftChild = leftChild; }else if(parent != null && parent.val < val){// 存在父节点,且node位置为父节点的右边 parent.rightChild = leftChild; }else{ root = leftChild; } return true; }else if(leftChild != null && rightChild != null){// 两个子节点都存在 Node successor = getSuccessor(node); // 这种状况,一定存在后继节点 int temp = successor.val; boolean delete = delete(temp); if(delete){ node.val = temp; } successor = null; return true; } return false; }/** * 找到node节点的后继节点 * 1、先判别该节点有没有右子树,假如有,则从右节点的左子树中寻觅后继节点,没有则停止下一步 * 2、查找该节点的父节点,若该父节点的右节点等于该节点,则继续寻觅父节点, *直至父节点为Null或找到不等于该节点的右节点。 * 理由,后继节点一定比该节点大,若存在右子树,则后继节点一定存在右子树中,这是第一步的理由 *若不存在右子树,则也可能存在该节点的某个祖父节点(即该节点的父节点,或更上层父节点)的右子树中, *对其迭代查找,若有,则返回该节点,没有则返回null * @param node * @return */ private Node getSuccessor(Node node){ if(node.rightChild != null){ Node rightChild = node.rightChild; while(rightChild.leftChild != null){ rightChild = rightChild.leftChild; } return rightChild; } Node parent = node.parent; while(parent != null && (node == parent.rightChild)){ node = parent; parent = parent.parent; } return parent; } 复制代码查找 查找也比拟简单,其真实增加的时分,曾经完成了。实践状况中,这局部能够抽出来单独办法。代码如下public Node getNode(int val){ Node temp = root; int t; do{ t = temp.val-val; if(t > 0){ temp = temp.leftChild; }else if(t < 0){ temp = temp.rightChild; }else{ return temp; } }while(temp != null); return null; }

复制代码
二叉搜索树遍历 在理解二叉搜索树的性质后,很分明的晓得,它的中序遍历是从小到大依次排列的,这里提供中序遍历代码
public void print(){ print(root); } private void print(Node root){ if(root != null){ print(root.leftChild); System.out.println(root.val); // 位置在中间,则中序,若在前面,则为先序,否则为后续 print(root.rightChild); } }

    推荐阅读