• 转 二叉树之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

            </article>
  • 相关阅读:
    VisualC#的菜单编程
    利用Mutex实现应用程序的单实例运行
    C#下实现动态系统托盘图标
    C#中TreeView组件使用方法初步
    VisualC#中实现窗体间的数据传递之一
    AJAX在VS2005中的简单应用
    LiteORM学习一:EntityObject 设计
    读书笔记:人月神话的博客积极的心态读后感
    LiteORM学习三:查询设计
    LiteORM学习二:数据库设计
  • 原文地址:https://www.cnblogs.com/buxl/p/9352290.html
Copyright © 2020-2023  润新知