二叉树之Java实现二叉树基本操作

参考自《Java数据结构与算法》

  • 定义一个节点类,使节点与二叉树操作分离

class Node {
	int  value;
	Node leftChild;
	Node rightChild;
	
	Node(int value) {
		this.value = value;
	}
	
	public void display() {
		System.out.print(this.value + "\t");
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return String.valueOf(value);
	}
}

  • 需要实现的二叉树操作

class BinaryTree {
	private Node root = null;
	
	BinaryTree(int value) {
		root = new Node(value);
		root.leftChild  = null;
		root.rightChild = null;
	}
	public Node findKey(int value) {}   //查找
        public String insert(int value) {}  //插入
        public void inOrderTraverse() {}    //中序遍历递归操作
        public void inOrderByStack() {}     //中序遍历非递归操作    
        public void preOrderTraverse() {}  //前序遍历
        public void preOrderByStack() {}   //前序遍历非递归操作
        public void postOrderTraverse() {} //后序遍历
        public void postOrderByStack() {}  //后序遍历非递归操作
        public int getMinValue() {} //得到最小(大)值
        public boolean delete(int value) {} //删除
}

  • 查找数据:

  public Node findKey(int value) {
		Node current = root;
		while (true) {
			if (value == current.value) {
				return current;
			} else if (value < current.value) {
				current = current.leftChild;
			} else if (value > current.value) {
				current = current.rightChild;
			}
			
			if (current == null) {
				return null;
			}
		}
	}

  • 插入数据:与查找数据类似,不同点在于当节点为空时,不是返回而是插入

  public String insert(int value) {
		String error = null;
		
		Node node = new Node(value);
		if (root == null) {
			root = node;
			root.leftChild  = null;
			root.rightChild = null;
		} else {
			Node current = root;
			Node parent = null;
			while (true) {
				if (value < current.value) {
					parent = current;
					current = current.leftChild;
					if (current == null) {
						parent.leftChild = node;
						break;
					}
				} else if (value > current.value) {
					parent = current;
					current = current.rightChild;
					if (current == null) {
						parent.rightChild = node;
						break;
					}
				} else {
					error = "having same value in binary tree";
				}	
			} // end of while
		}
		return error;
    }

  • 遍历数据:

            1)中序遍历:最常用的一种遍历方法

  /**
	 * //中序遍历(递归):
	 *    1、调用自身来遍历节点的左子树
	 *    2、访问这个节点
	 *    3、调用自身来遍历节点的右子树
	 */
	public void inOrderTraverse() {
		System.out.print("中序遍历:");
		inOrderTraverse(root);
		System.out.println();
	}
	
	private void inOrderTraverse(Node node) {
		if (node == null) 
			return ;
		
		inOrderTraverse(node.leftChild);
		node.display();
		inOrderTraverse(node.rightChild);
	}

               

/**
	 * 中序非递归遍历:
	 *     1)对于任意节点current,若该节点不为空则将该节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
	 *     2)若左子树为空,栈顶节点出栈,访问节点后将该节点的右子树置为current
	 *     3) 重复1、2步操作,直到current为空且栈内节点为空。
	 */
	public void inOrderByStack() {
		System.out.print("中序非递归遍历:");
		Stack<Node> stack = new Stack<Node>();
		Node current = root;
		while (current != null || !stack.isEmpty()) {
			while (current != null) {
				stack.push(current);
				current = current.leftChild;
			}
			
			if (!stack.isEmpty()) {
				current = stack.pop();
				current.display();
				current = current.rightChild;
			}
		}
		System.out.println();
	}

              2)前序遍历:

  /**
	 * //前序遍历(递归):
	 *    1、访问这个节点
	 *    2、调用自身来遍历节点的左子树
	 *    3、调用自身来遍历节点的右子树
	 */
	public void preOrderTraverse() {
		System.out.print("前序遍历:");
		preOrderTraverse(root);
		System.out.println();
	}
	
	private void preOrderTraverse(Node node) {
		if (node == null) 
			return ;
		
		node.display();
		preOrderTraverse(node.leftChild);
		preOrderTraverse(node.rightChild);
	}

               

/**
	 * 前序非递归遍历:
	 *     1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
	 *     2)若左子树为空,栈顶节点出栈,将该节点的右子树置为current
	 *     3) 重复1、2步操作,直到current为空且栈内节点为空。
	 */
	public void preOrderByStack() {
		System.out.print("前序非递归遍历:");
		Stack<Node> stack = new Stack<Node>();
		Node current = root;
		while (current != null || !stack.isEmpty()) {
			while (current != null) {
				stack.push(current);
				current.display();
				current = current.leftChild;
			}
			
			if (!stack.isEmpty()) {
				current = stack.pop();
				current = current.rightChild;
			}
		}
		System.out.println();
	}

             3)后序遍历:

  /**
	 * //后序遍历(递归):
	 *    1、调用自身来遍历节点的左子树
	 *    2、调用自身来遍历节点的右子树
	 *    3、访问这个节点
	 */
	public void postOrderTraverse() {
		System.out.print("后序遍历:");
		postOrderTraverse(root);
		System.out.println();
	}
	
	private void postOrderTraverse(Node node) {
		if (node == null) 
			return ;
		
		postOrderTraverse(node.leftChild);
		postOrderTraverse(node.rightChild);
		node.display();
	}
/**
	 * 后序非递归遍历:
	 *     1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
	 *     2)若左子树为空,取栈顶节点的右子树,如果右子树为空或右子树刚访问过,则访问该节点,并将preNode置为该节点
	 *     3) 重复1、2步操作,直到current为空且栈内节点为空。
	 */
	public void postOrderByStack() {
		System.out.print("后序非递归遍历:");
		Stack<Node> stack = new Stack<Node>();
		Node current = root;
		Node preNode = null;
		while (current != null || !stack.isEmpty()) {
			while (current != null) {
				stack.push(current);
				current = current.leftChild;
			}
			
			if (!stack.isEmpty()) {
				current = stack.peek().rightChild;
				if (current == null || current == preNode) {
					current = stack.pop();
					current.display();
					preNode = current;
					current = null;
				}
			}
		}
		System.out.println();
	}


  • 得到最小(大)值:依次向左(右)直到空为之
  public int getMinValue() {
		Node current = root;
		while (true) {
			if (current.leftChild == null)
				return current.value;
			
			current = current.leftChild;
		}
	}
  • 删除:删除操作很复杂,删除节点大致分为三种情况:
             1)删除节点为叶子节点

               

              2)删除节点只有一个子节点:只有一个左子节点和只有一个右子节点

               

              3)删除节点有两个子节点:这种情况比较复杂,需要寻找后继节点,即比要删除的节点的关键值次高的节点是它的后继节点。

            说得简单一些,后继节点就是比要删除的节点的关键值要大的节点集合中的最小值

            得到后继节点的代码如下:

  /**
	 * 
	 * 得到后继节点,即删除节点的左后代
	 */
	private Node getSuccessor(Node delNode) {
		Node successor = delNode;
		Node successorParent = null;
		Node current = delNode.rightChild;
		
		while (current != null) {
			successorParent = successor;
			successor = current;
			current = current.leftChild;
		}
		
		//如果后继节点不是删除节点的右子节点时,
		if (successor != delNode.rightChild) {
			//要将后继节点的右子节点指向后继结点父节点的左子节点,
			successorParent.leftChild = successor.rightChild;
			//并将删除节点的右子节点指向后继结点的右子节点
			successor.rightChild = delNode.rightChild;
		}
		//任何情况下,都需要将删除节点的左子节点指向后继节点的左子节点
		successor.leftChild = delNode.leftChild;
		
		return successor;
	}
                  a)如果后继节点是刚好是要删除节点的右子节点(此时可以知道,这个右子节点没有左子点,如果有,就不该这个右子节点为后继节点)

          

            

         //删除的节点为父节点的左子节点时:
         parent.leftChild = successor;
         successor.leftChild = delNode.leftChild;
              
         //删除的节点为父节点的右子节点时:
         parent.rightChild = successor;
         successor.leftChild = delNode.leftChild

 

             b)如果后继节点为要删除节点的右子节点的左后代:

             

            

        //删除的节点为父节点的左子节点时:
         successorParent.leftChild = successor.rightChild;
         successor.rightChild = delNode.rightChild;
          parent.leftChild = successor;
          successor.leftChild = delNode.leftChild;
              
          //删除的节点为父节点的右子节点时:
          successorParent.leftChild = successor.rightChild;
          successor.rightChild = delNode.rightChild;
          parent.rightChild = successor;
          successor.leftChild = delNode.leftChild;

                 综合以上各种情况,删除代码如下: 

  public boolean delete(int value) {
		Node current = root;    //需要删除的节点
		Node parent = null;     //需要删除的节点的父节点
		boolean isLeftChild = true;   //需要删除的节点是否父节点的左子树
		
		while (true) {
			if (value == current.value) {
				break;
			} else if (value < current.value) {
				isLeftChild = true;
				parent = current;
				current = current.leftChild;
			} else {
				isLeftChild = false;
				parent = current;
				current = current.rightChild;
			}
			
			//找不到需要删除的节点,直接返回
			if (current == null)
				return false;
		}
		
		//分情况考虑
		//1、需要删除的节点为叶子节点
		if (current.leftChild == null && current.rightChild == null) {
			//如果该叶节点为根节点,将根节点置为null
			if (current == root) {
				root = null;
			} else {
				//如果该叶节点是父节点的左子节点,将父节点的左子节点置为null
				if (isLeftChild) {
					parent.leftChild  = null;
				} else { //如果该叶节点是父节点的右子节点,将父节点的右子节点置为null
					parent.rightChild = null;
				}
			}
		} 
		//2、需要删除的节点有一个子节点,且该子节点为左子节点
		else if (current.rightChild == null) {
			//如果该节点为根节点,将根节点的左子节点变为根节点
			if (current == root) {
				root = current.leftChild;
			} else {
				//如果该节点是父节点的左子节点,将该节点的左子节点变为父节点的左子节点
				if (isLeftChild) {
					parent.leftChild = current.leftChild;
				} else {  //如果该节点是父节点的右子节点,将该节点的左子节点变为父节点的右子节点
					parent.rightChild = current.leftChild;
				}
			}
		}
		//2、需要删除的节点有一个子节点,且该子节点为右子节点
		else if (current.leftChild == null) {
			//如果该节点为根节点,将根节点的右子节点变为根节点
			if (current == root) {
				root = current.rightChild;
			} else {
				//如果该节点是父节点的左子节点,将该节点的右子节点变为父节点的左子节点
				if (isLeftChild) {
					parent.leftChild = current.rightChild;
				} else {  //如果该节点是父节点的右子节点,将该节点的右子节点变为父节点的右子节点
					parent.rightChild = current.rightChild;
				}
			}
		}
		//3、需要删除的节点有两个子节点,需要寻找该节点的后续节点替代删除节点
		else {
			Node successor = getSuccessor(current);
			//如果该节点为根节点,将后继节点变为根节点,并将根节点的左子节点变为后继节点的左子节点
			if (current == root) {
				root = successor;
			} else {
				//如果该节点是父节点的左子节点,将该节点的后继节点变为父节点的左子节点
				if (isLeftChild) {
					parent.leftChild = successor;
				} else {  //如果该节点是父节点的右子节点,将该节点的后继节点变为父节点的右子节点
					parent.rightChild = successor;
				}
			}
		}
		current = null;
		return true;
	}

  •                测试代码
    public class BinaryTreeDemo {
	    public static void main(String[] args) {
		BinaryTree bt = new BinaryTree(52);
		bt.insert(580);
		bt.insert(12);
		bt.insert(50);
		bt.insert(58);
		bt.insert(9);
		bt.insert(888);
		bt.insert(248);
		bt.insert(32);
		bt.insert(666);
		bt.insert(455);
		bt.insert(777);
		bt.insert(999);
		bt.inOrderTraverse();
		bt.preOrderTraverse();
		bt.postOrderTraverse();
		System.out.println(bt.findKey(32));
		System.out.println(bt.findKey(81));
		System.out.println("最小值:" + bt.getMinValue());
//		bt.delete(32);      //删除叶子节点
//		bt.delete(50);      //删除只有一个左子节点的节点
//		bt.delete(248);      //删除只有一个右子节点的节点
//		bt.delete(248);      //删除只有一个右子节点的节点
//		bt.delete(580);      //删除有两个子节点的节点,且后继节点为删除节点的右子节点的左后代
//		bt.delete(888);      //删除有两个子节点的节点,且后继节点为删除节点的右子节点
		bt.delete(52);       //删除有两个子节点的节点,且删除节点为根节点
		
		bt.inOrderTraverse();
	    }
    }
测试结果:

中序遍历:91232 50 52 58248455580 666777 888999
中序非递归遍历:9 12 32 50 5258248 455580 666777 888999
前序遍历:52 12 9 50 3258058 248 455 888 666 777 999
前序非递归遍历:52 12 9 50 3258058 248 455 888 666 777 999
后序遍历:9 32 50 12 45524858 777 666 999 888 580 52
后序非递归遍历:9 32 50 12 45524858 777 666 999 888 580 52
32
null
最小值:9
中序遍历:9 12 32 50 58248455580 666777 888999

  • 39
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值