定义结点结构
class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
public TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
定义二叉树以及写入遍历方法
ps:遍历方法中要写入根节点参数,否则在递归中每次都要为子树建立新树
import java.util.LinkedList;
public class BinaryTree {
TreeNode root;
static List<TreeNode> midTraversalList = new LinkedList<TreeNode>();//这是为了下方获取中序遍历的数组而定义的
public BinaryTree() {};
public BinaryTree(TreeNode root) {
this.root = root;
}
//前序遍历/深度遍历
public void beforeTraversal(TreeNode treeNode) {
if(treeNode == null) {
return;
}else {
System.out.print(treeNode.val);
beforeTraversal(treeNode.left);
beforeTraversal(treeNode.right);
}
}
//中序遍历
public void midTraversal(TreeNode treeNode) {
if(treeNode == null) {
return;
}else {
midTraversal(treeNode.left);
System.out.print(treeNode.val);
midTraversal(treeNode.right);
}
}
//后序遍历
public void afterTraversal(TreeNode treeNode) {
if(treeNode == null) {
return;
}else {
afterTraversal(treeNode.left);
afterTraversal(treeNode.right);
System.out.print(treeNode.val);
}
}
//层次遍历/广度遍历
public void layerTraversal(TreeNode treeNode) {
if(treeNode == null) {
return;
}else {
//借助队列的先进先出特性
LinkedList<TreeNode> ll = new LinkedList<TreeNode>();
ll.add(treeNode);
TreeNode tn;
//注意此处不能用null判别,否则就算列表为空也会进入循环体
while(!ll.isEmpty()) {
//把一个结点弹出的同时,把它的儿子们都放进去
tn = ll.poll();
System.out.print(tn.val);
if(tn.left != null) {
ll.add(tn.left);
}
if(tn.right != null) {
ll.add(tn.right);
}
}
}
}
}
定义搜索二叉树的创建、增、删、查的方法
//搜索二叉树的插入
//思路:若根节点为空,直接赋值;
//否则,定义一个新节点等于root,再定义一个循环;
//判断插入值与新节点的大小关系,若小于,则判断其左子节点是否为空;
//为空则赋值,结束;否则就使新节点为其左子节点,继续循环;
//若大于则比较右子节点。相同操作。
public void insert(TreeNode tn) {
if(tn == null) {
return;
}
else if(root == null) {
root = tn;
}else {
TreeNode newRoot = root;
while(true) {
if(tn.val<newRoot.val) {
//为空就直接插入
if(newRoot.left == null) {
newRoot.left = tn;
break;
}else {
newRoot = newRoot.left;
}
}else if(tn.val>newRoot.val){
if(newRoot.right == null) {
newRoot.right = tn;
break;
}else {
newRoot = newRoot.right;
}
}else {
System.out.println("插入值不合法!");
break;
}
}
}
}
//搜索二叉树的创建
//哈哈,其实就是定义在插入的基础上,操作一样,传入多个节点而已
public static BinaryTree create(List<TreeNode> treeNodes) {
if(treeNodes.size() == 0) {
return null;
}
BinaryTree bt = new BinaryTree(treeNodes.get(0));
for(int i=1; i<treeNodes.size(); i++) {
bt.insert(treeNodes.get(i));
}
return bt;
}
//搜索二叉树的查找val
//思路:先判断根节点是否为空;若不是,再判断其值是不是等于查找值;
// 若不等于,再根据其与查找值的大小关系来递归其子树
//因此,此处传根节点是为了便于递归传参,传新节点
public static TreeNode search(TreeNode root, int val) {
if(null == root) {
return null;
}else {
if(val == root.val) {
return root;
}else if(val < root.val) {
return search(root.left, val);
}else {
return search(root.right, val);
}
}
}
//找到目标结点的双亲结点
public static TreeNode searchParent(TreeNode root, int val) {
TreeNode parent;
if(null == root) {
return null;
}else {
if(val == root.val) {
return null;
}else if(val < root.val) {
if(null == root.left) {
return null;
}
if(root.left.val == val) {
return root;
}
return searchParent(root.left, val);
}else {
if(null == root.right) {
return null;
}
if(root.right.val == val) {
return root;
}
return searchParent(root.right, val);
}
}
}
//二叉树的删除
/*要注意,删除操作不是将目标节点置空,而是先判断其为左/右子节点,然后查找其双亲结点;
将其双亲结点的左 or右子节点置空。
原因依旧是Java的传值问题,直接置空只是将新定义的指向所查找结点的指针指向别处了。
是不是想起了链表的删除也是....其实要找的是他的前一结点.....
思路很简单,(以下为目标节点不为root的情况,若为root,则直接按需使用换值法即可)
无子节点时:直接将其parent指向目标节点的指针置空;
单个子节点时:保存——置空——将子节点直接置于目标节点的位置;
多个子节点时,方式1:
1、保存被删结点的子节点
2、将被删结点的双亲结点的目标孩子置空;
3、把被删结点的子节点插入回树中
方式2:
1、获得目标节点在中序遍历中的后一节点并保存其值;
2、删除上方所获节点(此时已转化为删除单节点 or 无节点情况)
3、将目标节点的值赋为刚刚保存的所获节点值(据BST性质,这个值放在这里是合理的)
ps:为了实现此方法,还另外定义了一个寻找其双亲的方法and将孩子置空的方法and找到其中序遍历的上下一节点的方法
*/
public boolean delete(int val) {
String flag = "";
if(null == root) {
return false;
}
//先在树中找到值所在节点
TreeNode tn = search(root, val);
TreeNode parent = searchParent(root, val);
//假如找不到目标节点
if(null == tn) {
return false;
}
if(null != parent) {
//假如找得到其双亲结点,判断其为双亲结点的左还是右子节点;也可能找不到,譬如root
if(parent.left == tn) {
flag = "left";
}else {
flag = "right";
}
}
//定义用于保存被删结点的左、右子节点的结点
TreeNode leftAfter;
TreeNode rightAfter;
//假如被删结点刚好为叶子节点
if( null == tn.left && null == tn.right) {
if(val == root.val) { //如果是根节点那么直接删除根节点即可
root = null;
return true;
}else {
setNull(parent, flag);
return true;
}
}
//假如只有左子树
else if(null != tn.left && null == tn.right) {
if(val == root.val) { //为根节点,直接让其与直接前驱换值并删除直接前驱
TreeNode preSuccessor = getPreSuccessor(root);
int value = preSuccessor.val;
delete(value);
root.val = value;
}else {
leftAfter = tn.left; //先把有数据的节点保存下来
setNull(parent, flag);
if(flag == "left") { //把剩余节点置于被删处(此处利用了搜索二叉树每个子树都是搜索二叉树的性质,
parent.left = leftAfter; //即树中任一节点的左/右子树中所有节点都比它小/大,
}else { //所以被删结点在哪边,就把剩下的插在哪边就好)
parent.right = leftAfter;
}
}
return true;
}
//假如只有右子树
else if(null == tn.left && null != tn.right) {
if(val == root.val) {
TreeNode successor = getSuccessor(root);
int value = successor.val;
delete(value);
root.val = value;
}else {
rightAfter = tn.right;
setNull(parent, flag);
if(flag == "left") {
parent.left = rightAfter;
}else {
parent.right = rightAfter;
}
}
return true;
}
//假如有左、右子树(此时不涉及双亲,不管是不是根节点处理方法都一致)
// 方式1:值互换的方式:
// 找到目标节点中序遍历中的后一节点,即其所有右子节点中的最小节点,将其值保存;
// 将所得节点删掉,然后再把保存的值赋给目标节点。
// 根据BST的性质,可知继承节点一定比目标节点左子树中的节点大,比右子树中的节点小,
// 所以目标节点删除后,采用它来取代是合理的;
// 又根据中序遍历的性质,可知所得节点一定只有<=1个子节点,就可以采用上面方式解决了;
else {
TreeNode successor = getSuccessor(tn);
int successorVal = successor.val;
delete(successorVal);
tn.val = successorVal;
return true;
}
// 方式2:直接将目标节点删除,再把其后续节点插入,更加简单易懂,且效率与上种方式相差不大
// ps:当然,只有单节点也可以借用这个思路,但那种情况使用上面方式效率会更高。
// else {
// leftAfter = tn.left;
// rightAfter = tn.right; //保存
// setNull(parent, flag); //删除
// insert(leftAfter); //插入后续结点
// insert(rightAfter);
// return true;
// }
}
//把某个结点的左/右子树置空
public static void setNull(TreeNode parent, String flag) {
if(flag == "left") {
parent.left = null;
}
if(flag == "right") {
parent.right = null;
}
}
//获得以treeNode为根节点的中序遍历的数组
public static void getMidTraversal(TreeNode treeNode) {
if(treeNode == null) {
return;
}else {
getMidTraversal(treeNode.left);
midTraversalList.add(treeNode);
getMidTraversal(treeNode.right);
}
}
//获得树中值比treeNode大的最小值节点,即“继承节点”
public static TreeNode getSuccessor(TreeNode treeNode) {
//先把中序遍历所得置入数组
getMidTraversal(treeNode);
//获取列表中目标节点的index
int index = midTraversalList.indexOf(treeNode);
//获取中序遍历中目标节点的下一个,也就是树中比根节点大的最小值
return midTraversalList.get(index+1);
}
BST的改进☞BBST(平衡搜索二叉树的调节
ps:以下方法依旧在上方树的方法中添加
//获取某节点高度
public int getHeight(TreeNode tn){
if(null == tn) {
return 0;
}else {
//对所有节点的左右子树都进行了遍历,且每一次只返回了较大的一个
int left = getHeight(tn.left);
int right = getHeight(tn.right);
return left>right?left:right + 1;
}
}
//获取节点的平衡因子
public int getBalance(TreeNode tn) throws Exception {
if(null == tn) {
throw new Exception("无效节点!");
}
int leftHeight = getHeight(tn.left);
int rightHeight = getHeight(tn.right);
return leftHeight-rightHeight;
}
//对某个可能失衡的节点进行调节
//返回调节后的新根节点,目的是为了可以更新根节点
//失衡的情况共有四种,类别和处理方式已注释在代码中,看不懂可以画图
//举个栗子,左旋,其实就是因为右子树高度太高,把根节点右移(代码实现也就是修改父子关系),
//但过程中要记得顺把新根节点的左子树转移到原根节点的右子节点上
//有些时候就是两次旋转也不能平衡,依实际情况调节
public TreeNode balanceNode(TreeNode tn) throws Exception {
if(null == tn) {
return null;
}
int balance = getBalance(tn);
TreeNode newRoot;
TreeNode newRoot2;
if(balance>=-1 && balance<=1) { //本身就平衡,无需调节
return tn;
}else if(balance<-1){ //右子树高度过高
if(getBalance(tn.right)>=0) { //右子树中的左子树高度>=右子树
newRoot = rotateRight(tn.right); //1、右左型:目标节点的右子节点右旋、再目标节点左旋
tn.right = newRoot; //更新右子节点
newRoot2 = rotateLeft(tn);
return newRoot2;
}else { //2、右右型:左旋
newRoot = rotateLeft(tn);
return newRoot;
}
}else { //左子树高度过高
if(getBalance(tn.left)>=0) { //3、左左型:右旋
newRoot = rotateRight(tn);
return newRoot;
}else { //4、左右型:目标节点的左子节点左旋后、目标节点右旋
newRoot = rotateLeft(tn.left);
tn.left = newRoot;
newRoot2 = rotateRight(tn);
return newRoot2;
}
}
}
//左旋算法
//其实就是将以目标节点为根节点的子树 的根节点更换为目标节点的右子节点,并将其左子树插到目标节点的右子树
public TreeNode rotateLeft(TreeNode tn) {
TreeNode newTn = tn.right; //找到子树中新的根节点,也就是目标节点的右子节点
if(null != newTn.left) {
TreeNode res = newTn.left; //把新根节点的原左子树保存下来,毕竟人家一会要接目标节点为左子树了
tn.right = res; //把保存的子树插入到目标节点的右子节点中
}else {
tn.right = null;
}
newTn.left = tn; //建立两个节点的新关系
return newTn;
}
//右旋算法
public TreeNode rotateRight(TreeNode tn) {
TreeNode newTn = tn.left;
if(null != newTn.right) {
TreeNode res = newTn.right;
tn.left = res;
}else {
tn.left = null;
}
newTn.right = tn;
return newTn;
}
测试类
import java.util.ArrayList;
import java.util.List;
public class test {
public static void main(String[] args) throws Exception {
TreeNode root = new TreeNode(5);
TreeNode tn2 = new TreeNode(3);
TreeNode tn3 = new TreeNode(2);
TreeNode tn4 = new TreeNode(4);
// TreeNode tn5 = new TreeNode(6);
// TreeNode tn6 = new TreeNode(7);
// TreeNode tn7 = new TreeNode(8);
// TreeNode tn8 = new TreeNode(1);
// TreeNode tn9 = new TreeNode(11);
// TreeNode tn10 = new TreeNode(15);
// TreeNode tn11 = new TreeNode(12);
// TreeNode tn12 = new TreeNode(44);
// TreeNode tn13 = new TreeNode(66);
// BinaryTree bt = new BinaryTree(root);
// System.out.println(bt.root.val);
// bt.insert(tn2);
// bt.insert(tn3);
// bt.insert(tn4);
// root.left = tn2;
// root.right = tn3;
// tn2.left = tn4;
// tn2.right = tn5;
List<TreeNode> treeNodes = new ArrayList();
treeNodes.add(root);
treeNodes.add(tn2);
treeNodes.add(tn3);
treeNodes.add(tn4);
// treeNodes.add(tn5);
// treeNodes.add(tn6);
// treeNodes.add(tn7);
// treeNodes.add(tn8);
// treeNodes.add(tn9);
// treeNodes.add(tn10);
// treeNodes.add(tn11);
// treeNodes.add(tn12);
// treeNodes.add(tn13);
BinaryTree bt = BinaryTree.create(treeNodes);
// bt.delete(3);
// bt.beforeTraversal(bt.root);System.out.println();
// bt.midTraversal(bt.root);System.out.println();
// bt.afterTraversal(bt.root);System.out.println();
bt.layerTraversal(bt.root);
System.out.println();
System.out.println(bt.getBalance(root));
TreeNode newRoot = bt.balanceNode(bt.root);
bt.root = newRoot; //为什么非要这么麻烦地返回来更新,又要提到Java的传值问题啦
bt.layerTraversal(bt.root);
System.out.println();
System.out.println(bt.getBalance(root));
// TreeNode tn = bt.searchParent(root, 6);
// System.out.println(tn.val);
// TreeNode tn = bt.getSuccessor(root);
// System.out.println(tn.val);
// bt.getMidTraversal(root);
// System.out.println(bt.midTravesalList.size());
// System.out.println(successor.val);
// TreeNode tn0 = bt.search(bt.root, 3);
// System.out.println(tn0.val);
}
}