转 二叉树之Java实现二叉树基本操作
Posted buxl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转 二叉树之Java实现二叉树基本操作相关的知识,希望对你有一定的参考价值。
参考自《Java数据结构与算法》
- 定义一个节点类,使节点与二叉树操作分离
- class Node {
- int value;
- Node leftChild;
- Node rightChild;
-
- Node(int value) {
- this.value = value;
- }
-
- public void display() {
- System.out.print(this.value + " ");
- }
-
- @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;
- }
- }
- 删除:删除操作很复杂,删除节点大致分为三种情况:
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
以上是关于转 二叉树之Java实现二叉树基本操作的主要内容,如果未能解决你的问题,请参考以下文章
两万字硬核解析树与二叉树所有基本操作(包含堆,链式二叉树基本操作及测试代码,和递归函数的书写方法)