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

Posted buxl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转 二叉树之Java实现二叉树基本操作相关的知识,希望对你有一定的参考价值。

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

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

  1. class Node {
  2. int value;
  3. Node leftChild;
  4. Node rightChild;
  5. Node(int value) {
  6. this.value = value;
  7. }
  8. public void display() {
  9. System.out.print(this.value + " ");
  10. }
  11. @Override
  12. public String toString() {
  13. // TODO Auto-generated method stub
  14. return String.valueOf(value);
  15. }
  16. }

  • 需要实现的二叉树操作

  1. class BinaryTree {
  2. private Node root = null;
  3. BinaryTree(int value) {
  4. root = new Node(value);
  5. root.leftChild = null;
  6. root.rightChild = null;
  7. }
  8. public Node findKey(int value) {} //查找
  9. public String insert(int value) {} //插入
  10. public void inOrderTraverse() {} //中序遍历递归操作
  11. public void inOrderByStack() {} //中序遍历非递归操作
  12. public void preOrderTraverse() {} //前序遍历
  13. public void preOrderByStack() {} //前序遍历非递归操作
  14. public void postOrderTraverse() {} //后序遍历
  15. public void postOrderByStack() {} //后序遍历非递归操作
  16. public int getMinValue() {} //得到最小(大)值
  17. public boolean delete(int value) {} //删除
  18. }

  • 查找数据:

  1. public Node findKey(int value) {
  2. Node current = root;
  3. while (true) {
  4. if (value == current.value) {
  5. return current;
  6. } else if (value < current.value) {
  7. current = current.leftChild;
  8. } else if (value > current.value) {
  9. current = current.rightChild;
  10. }
  11. if (current == null) {
  12. return null;
  13. }
  14. }
  15. }

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

  1. public String insert(int value) {
  2. String error = null;
  3. Node node = new Node(value);
  4. if (root == null) {
  5. root = node;
  6. root.leftChild = null;
  7. root.rightChild = null;
  8. } else {
  9. Node current = root;
  10. Node parent = null;
  11. while (true) {
  12. if (value < current.value) {
  13. parent = current;
  14. current = current.leftChild;
  15. if (current == null) {
  16. parent.leftChild = node;
  17. break;
  18. }
  19. } else if (value > current.value) {
  20. parent = current;
  21. current = current.rightChild;
  22. if (current == null) {
  23. parent.rightChild = node;
  24. break;
  25. }
  26. } else {
  27. error = "having same value in binary tree";
  28. }
  29. } // end of while
  30. }
  31. return error;
  32. }

  • 遍历数据:

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

  1. /**
  2. * //中序遍历(递归):
  3. * 1、调用自身来遍历节点的左子树
  4. * 2、访问这个节点
  5. * 3、调用自身来遍历节点的右子树
  6. */
  7. public void inOrderTraverse() {
  8. System.out.print("中序遍历:");
  9. inOrderTraverse(root);
  10. System.out.println();
  11. }
  12. private void inOrderTraverse(Node node) {
  13. if (node == null)
  14. return ;
  15. inOrderTraverse(node.leftChild);
  16. node.display();
  17. inOrderTraverse(node.rightChild);
  18. }

               

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

              2)前序遍历:

  1. /**
  2. * //前序遍历(递归):
  3. * 1、访问这个节点
  4. * 2、调用自身来遍历节点的左子树
  5. * 3、调用自身来遍历节点的右子树
  6. */
  7. public void preOrderTraverse() {
  8. System.out.print("前序遍历:");
  9. preOrderTraverse(root);
  10. System.out.println();
  11. }
  12. private void preOrderTraverse(Node node) {
  13. if (node == null)
  14. return ;
  15. node.display();
  16. preOrderTraverse(node.leftChild);
  17. preOrderTraverse(node.rightChild);
  18. }

               

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

             3)后序遍历:

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


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

               技术分享图片

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

               技术分享图片

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

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

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

  1. /**
  2. *
  3. * 得到后继节点,即删除节点的左后代
  4. */
  5. private Node getSuccessor(Node delNode) {
  6. Node successor = delNode;
  7. Node successorParent = null;
  8. Node current = delNode.rightChild;
  9. while (current != null) {
  10. successorParent = successor;
  11. successor = current;
  12. current = current.leftChild;
  13. }
  14. //如果后继节点不是删除节点的右子节点时,
  15. if (successor != delNode.rightChild) {
  16. //要将后继节点的右子节点指向后继结点父节点的左子节点,
  17. successorParent.leftChild = successor.rightChild;
  18. //并将删除节点的右子节点指向后继结点的右子节点
  19. successor.rightChild = delNode.rightChild;
  20. }
  21. //任何情况下,都需要将删除节点的左子节点指向后继节点的左子节点
  22. successor.leftChild = delNode.leftChild;
  23. return successor;
  24. }
                 a)如果后继节点是刚好是要删除节点的右子节点(此时可以知道,这个右子节点没有左子点,如果有,就不该这个右子节点为后继节点)

          技术分享图片

            

  1. //删除的节点为父节点的左子节点时:
  2. parent.leftChild = successor;
  3. successor.leftChild = delNode.leftChild;
  4. //删除的节点为父节点的右子节点时:
  5. parent.rightChild = successor;
  6. successor.leftChild = delNode.leftChild


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

             技术分享图片

            

  1. //删除的节点为父节点的左子节点时:
  2. successorParent.leftChild = successor.rightChild;
  3. successor.rightChild = delNode.rightChild;
  4. parent.leftChild = successor;
  5. successor.leftChild = delNode.leftChild;
  6. //删除的节点为父节点的右子节点时:
  7. successorParent.leftChild = successor.rightChild;
  8. successor.rightChild = delNode.rightChild;
  9. parent.rightChild = successor;
  10. successor.leftChild = delNode.leftChild;


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

  1. public boolean delete(int value) {
  2. Node current = root; //需要删除的节点
  3. Node parent = null; //需要删除的节点的父节点
  4. boolean isLeftChild = true; //需要删除的节点是否父节点的左子树
  5. while (true) {
  6. if (value == current.value) {
  7. break;
  8. } else if (value < current.value) {
  9. isLeftChild = true;
  10. parent = current;
  11. current = current.leftChild;
  12. } else {
  13. isLeftChild = false;
  14. parent = current;
  15. current = current.rightChild;
  16. }
  17. //找不到需要删除的节点,直接返回
  18. if (current == null)
  19. return false;
  20. }
  21. //分情况考虑
  22. //1、需要删除的节点为叶子节点
  23. if (current.leftChild == null && current.rightChild == null) {
  24. //如果该叶节点为根节点,将根节点置为null
  25. if (current == root) {
  26. root = null;
  27. } else {
  28. //如果该叶节点是父节点的左子节点,将父节点的左子节点置为null
  29. if (isLeftChild) {
  30. parent.leftChild = null;
  31. } else { //如果该叶节点是父节点的右子节点,将父节点的右子节点置为null
  32. parent.rightChild = null;
  33. }
  34. }
  35. }
  36. //2、需要删除的节点有一个子节点,且该子节点为左子节点
  37. else if (current.rightChild == null) {
  38. //如果该节点为根节点,将根节点的左子节点变为根节点
  39. if (current == root) {
  40. root = current.leftChild;
  41. } else {
  42. //如果该节点是父节点的左子节点,将该节点的左子节点变为父节点的左子节点
  43. if (isLeftChild) {
  44. parent.leftChild = current.leftChild;
  45. } else { //如果该节点是父节点的右子节点,将该节点的左子节点变为父节点的右子节点
  46. parent.rightChild = current.leftChild;
  47. }
  48. }
  49. }
  50. //2、需要删除的节点有一个子节点,且该子节点为右子节点
  51. else if (current.leftChild == null) {
  52. //如果该节点为根节点,将根节点的右子节点变为根节点
  53. if (current == root) {
  54. root = current.rightChild;
  55. } else {
  56. //如果该节点是父节点的左子节点,将该节点的右子节点变为父节点的左子节点
  57. if (isLeftChild) {
  58. parent.leftChild = current.rightChild;
  59. } else { //如果该节点是父节点的右子节点,将该节点的右子节点变为父节点的右子节点
  60. parent.rightChild = current.rightChild;
  61. }
  62. }
  63. }
  64. //3、需要删除的节点有两个子节点,需要寻找该节点的后续节点替代删除节点
  65. else {
  66. Node successor = getSuccessor(current);
  67. //如果该节点为根节点,将后继节点变为根节点,并将根节点的左子节点变为后继节点的左子节点
  68. if (current == root) {
  69. root = successor;
  70. } else {
  71. //如果该节点是父节点的左子节点,将该节点的后继节点变为父节点的左子节点
  72. if (isLeftChild) {
  73. parent.leftChild = successor;
  74. } else { //如果该节点是父节点的右子节点,将该节点的后继节点变为父节点的右子节点
  75. parent.rightChild = successor;
  76. }
  77. }
  78. }
  79. current = null;
  80. return true;
  81. }

  •                测试代码
  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. BinaryTree bt = new BinaryTree(52);
  4. bt.insert(580);
  5. bt.insert(12);
  6. bt.insert(50);
  7. bt.insert(58);
  8. bt.insert(9);
  9. bt.insert(888);
  10. bt.insert(248);
  11. bt.insert(32);
  12. bt.insert(666);
  13. bt.insert(455);
  14. bt.insert(777);
  15. bt.insert(999);
  16. bt.inOrderTraverse();
  17. bt.preOrderTraverse();
  18. bt.postOrderTraverse();
  19. System.out.println(bt.findKey(32));
  20. System.out.println(bt.findKey(81));
  21. System.out.println("最小值:" + bt.getMinValue());
  22. // bt.delete(32); //删除叶子节点
  23. // bt.delete(50); //删除只有一个左子节点的节点
  24. // bt.delete(248); //删除只有一个右子节点的节点
  25. // bt.delete(248); //删除只有一个右子节点的节点
  26. // bt.delete(580); //删除有两个子节点的节点,且后继节点为删除节点的右子节点的左后代
  27. // bt.delete(888); //删除有两个子节点的节点,且后继节点为删除节点的右子节点
  28. bt.delete(52); //删除有两个子节点的节点,且删除节点为根节点
  29. bt.inOrderTraverse();
  30. }
  31. }
测试结果:

中序遍历: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实现二叉树基本操作的主要内容,如果未能解决你的问题,请参考以下文章

二叉树基本操作方法的递归调用实现

两万字硬核解析树与二叉树所有基本操作(包含堆,链式二叉树基本操作及测试代码,和递归函数的书写方法)

数据结构与算法(周鹏-未出版)-第六章 树-6.3 二叉树基本操作的实现

C站万字详解二叉树基本操作演示程序(附图)

二叉搜索树基本操作实现

二叉树的应用--二叉树排序树基本操作