飞道的博客

二叉树的遍历、查找、删除

253人阅读  评论(0)

我们最初学习数据结构的时候,肯定是先从线性结构和链式结构讲起,回顾一下他们的特点。

线性结构以数组为例,它通过下标的方式访问元素,访问速度很快,但是当我们向数组中插入或删除某个元素时,会将插入位置的元素整体移动,从而造成效率低下。

链式结构以单链表为例,它在插入或删除元素时,只改变链表的指向并且不移动元素,能够解决线性结构插入或删除元素效率不足的问题,但是当我们需要访问某个元素时,只能从单链表头依次循环直到找到待访问元素为止,因此访问元素效率低下。

那么有没有哪种数据结构,既可以提高访问元素的速度,又可以提高插入或删除元素的效率呢?

它来了,能够提高数据存储和读取效率的树来了。以二叉排序树为例,它既可以保证数据的检索速度,又能够保证数据的插入、删除、修改的速度。

那么什么是二叉树呢? 每个节点最多只能有两个子节点的一种树称为二叉树。

二叉树又有满二叉树和完全二叉树:

有了二叉树的概念,就要用代码来实现它。

二叉树结构的创建:二叉树拥有左右结点和父结点,根据这个特点来创建下图所示的二叉树结构。

  创建具有左右结点的单个结点:


  
  1. class HeroNode{
  2. private int no;
  3. private String name;
  4. private HeroNode left; // 左结点
  5. private HeroNode right; // 右结点
  6. // 单个二叉树结点的操作
  7. // ...
  8. }

创建带有头结点的二叉树:


  
  1. class BinaryTree{
  2. private HeroNode root; // 头结点
  3. // 将单个结点的二叉树结点连接起来的操作
  4. // ...
  5. }

二叉树的遍历:前序遍历、中序遍历、后续遍历。三种遍历的区别在于,父结点所在的位置,父结点在前为前序遍历、在中为中序遍历、在后为后续遍历。前序遍历:父--->左--->右;中序遍历:左--->父--->右;后续遍历:右--->左--->父;

三种遍历的前提条件是初始化父结点root。

 前序遍历思路:

  1. 先输出当前结点(初始为root结点);
  2. 如果当前结点的左子结点不为空,则递归前序遍历;
  3. 如果当前结点的右子结点不为空,则递归前序遍历;

中序遍历思路:

  1. 如果当前结点的左子结点不为空,则递归中序遍历;
  2. 输出当前结点(初始为root结点);
  3. 如果当前结点的右子结点不为空,则递归中序遍历;

后序遍历思路:

  1. 如果当前结点的左子结点不为空,则递归后序遍历;
  2. 如果当前结点的右子结点不为空,则递归后序遍历;
  3. 输出当前结点(初始为root结点); 

  
  1. class BinaryTree{
  2. private HeroNode root; // 根结点
  3. public BinaryTree(HeroNode root){ // 指定根结点
  4. this.root = root;
  5. }
  6. // 前序遍历
  7. public void preOrder(){
  8. if ( this.root != null){
  9. this.root.preOrder(); // 调用HeroNode中的前序遍历方法
  10. } else {
  11. System.out.println( "二叉树为空,无法遍历");
  12. }
  13. }
  14. // 中序遍历
  15. public void infixOrder(){
  16. if ( this.root != null){
  17. this.root.infixOrder();
  18. } else {
  19. System.out.println( "二叉树为空,无法遍历");
  20. }
  21. }
  22. // 后序遍历
  23. public void postOrder(){
  24. if ( this.root != null){
  25. this.root.postOrder();
  26. } else {
  27. System.out.println( "二叉树为空,无法遍历");
  28. }
  29. }
  30. }

  
  1. class HeroNode{
  2. private int no;
  3. private String name;
  4. private HeroNode left; // 左结点
  5. private HeroNode right; // 右结点
  6. public HeroNode(int no, String name) {
  7. this.no = no;
  8. this.name = name;
  9. }
  10. // 省略getter、setter方法以及toString方法
  11. // 前序遍历
  12. public void preOrder(){
  13. // 1.输出当前父结点
  14. System.out.println( this);
  15. // 2.如果当前结点的左子结点不为空,前序遍历
  16. if ( this.left != null){
  17. this.left.preOrder();
  18. }
  19. // 3.如果当前结点的右子结点不为空,前序遍历
  20. if ( this.right != null){
  21. this.right.preOrder();
  22. }
  23. }
  24. // 中序遍历
  25. public void infixOrder(){
  26. // 1.如果当前结点的左子结点不为空,中序遍历
  27. if ( this.left != null){
  28. this.left.infixOrder();
  29. }
  30. // 2.输出当前父结点
  31. System.out.println( this);
  32. // 3.如果当前结点的右子结点不为空,中序遍历
  33. if ( this.right != null){
  34. this.right.infixOrder();
  35. }
  36. }
  37. // 后序遍历
  38. public void postOrder(){
  39. // 1.如果当前结点的左子结点不为空,中序遍历
  40. if ( this.left != null){
  41. this.left.infixOrder();
  42. }
  43. // 2.如果当前结点的右子结点不为空,中序遍历
  44. if ( this.right != null){
  45. this.right.infixOrder();
  46. }
  47. // 3.输出当前父结点
  48. System.out.println( this);
  49. }
  50. }

  
  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. HeroNode heroNode1 = new HeroNode( 1, "宋江");
  4. HeroNode heroNode2 = new HeroNode( 2, "吴用");
  5. HeroNode heroNode3 = new HeroNode( 3, "武松");
  6. HeroNode heroNode4 = new HeroNode( 4, "林冲");
  7. BinaryTree binaryTree = new BinaryTree(heroNode1); // 指定根结点
  8. heroNode1.setLeft(heroNode2); // 根节点的左子结点
  9. heroNode1.setRight(heroNode3); // 根节点的右子结点
  10. heroNode3.setRight(heroNode4); // 根节点的右子结点的右子结点
  11. // 遍历
  12. System.out.println( "前序遍历:");
  13. binaryTree.preOrder(); //前序遍历
  14. System.out.println( "中序遍历:");
  15. binaryTree.infixOrder(); //中序遍历
  16. System.out.println( "后序遍历:");
  17. binaryTree.postOrder(); //后序遍历
  18. }
  19. }

二叉树的查找:查找的思路和遍历的思路类似,只是多了一步判断查找条件是否相等。查找也分为三种,前序查找、中序查找和后序查找,三种查找的区别还是根据处理父结点的顺序。

前序查找:

  1. 先判断当前结点的no值是否等于要查找的no值,如果相等,就返回当前结点;
  2. 如果不等,先判断当前结点的左结点是否为空,不为空就向左递归前序查找,如果找到,返回当前结点,
  3. 如果没找到先判断当前结点的右子结点是否为空,不为空就向右子结点递归前序查找,如果找到,就返回当前结点,
  4. 没找到就返回null。
  5. 注:一定要有先判断左右结点是否为空这个条件,否则会出现空指针异常。

中序查找:

  1. 先判断当前结点的左子结点是否为空,不为空则向左结点递归中序查找,如果相等,就返回当前结点,
  2. 如果不等,就和当前结点比较,
  3. 如果当前结点和要查找的no值相等,就返回,不等就先判断右子节点是否为空,不为空则向右结点递归中序查找,
  4. 向右结点递归中序查找,找到就返回当前结点的右子结点,找不到返回null。

后序查找思路:

  1. 先判断当前结点的左子结点是否为空,不为空则向左结点递归后序查找,如果相等,就返回当前结点的左子结点,
  2. 如果不等,就判断当前结点的右子节点是否为空,如果不为空就向右子节点递归后序查找,
  3. 如果找到就返回,没找到就和当前结点比较,如果找到就返回,没找到就返回null。


  
  1. class HeroNode{
  2. private int no;
  3. private String name;
  4. private HeroNode left; // 左结点
  5. private HeroNode right; // 右结点
  6. // 省略getter、setter和构造方法等
  7. // 查找:
  8. // 前序查找
  9. public HeroNode preOrderSearch(int id){
  10. // 1.判断id和当前结点是否相等
  11. if ( this.no == id){
  12. return this;
  13. }
  14. // 2.当前结点没有找到,向左子结点递归前序查找
  15. HeroNode temp = null; // 用来接收递归是否找到相等元素
  16. if ( this.left != null){
  17. temp = this.left.preOrderSearch(id);
  18. }
  19. // 向左子结点递归找到了
  20. if (temp != null){
  21. return temp;
  22. }
  23. // 3.当前左子结点没有找到,向右子结点递归前序查找
  24. if ( this.right != null){
  25. temp = this.right.preOrderSearch(id);
  26. }
  27. // 返回右子结点是否找到,不用判断temp是否为null,可以在函数调用的地方来判断
  28. return temp;
  29. }
  30. // 中序查找
  31. public HeroNode infixOrderSearch(int id){
  32. // 1.当前结点的左子结点是否相等,判断是否找到
  33. HeroNode temp = null; // 用来判断递归结果是否找到
  34. if ( this.left != null){
  35. temp = this.left.infixOrderSearch(id);
  36. }
  37. if (temp != null){
  38. return temp;
  39. }
  40. // 2.当前结点是否相等
  41. if ( this.no == id){
  42. return this;
  43. }
  44. // 3.当前结点的右子节点是否相等,判断是否找到
  45. if ( this.right != null){
  46. temp = this.right.infixOrderSearch(id);
  47. }
  48. return temp;
  49. }
  50. // 后序查找
  51. public HeroNode postOrderSearch(int id){
  52. // 1.当前结点的左子结点是否相等,判断是否找到
  53. HeroNode temp = null; // 用来判断递归结果是否找到
  54. if ( this.left != null){
  55. temp = this.left.infixOrderSearch(id);
  56. }
  57. if (temp != null){
  58. return temp;
  59. }
  60. // 2.当前结点的右子节点是否相等,判断是否找到
  61. if ( this.right != null){
  62. temp = this.right.infixOrderSearch(id);
  63. }
  64. if (temp != null){
  65. return temp;
  66. }
  67. // 3.当前结点是否相等
  68. if ( this.no == id){
  69. return this;
  70. }
  71. return temp;
  72. }
  73. }

  
  1. class BinaryTree{
  2. private HeroNode root; // 根结点
  3. public BinaryTree(HeroNode root){ // 指定根结点
  4. this.root = root;
  5. }
  6. // 前序查找
  7. public HeroNode preOrderSearch(int id){
  8. if ( this.root != null){
  9. return this.root.preOrderSearch(id);
  10. }
  11. return null;
  12. }
  13. // 中序查找
  14. public HeroNode infixOrderSearch(int id){
  15. if ( this.root != null){
  16. return this.root.infixOrderSearch(id);
  17. }
  18. return null;
  19. }
  20. // 后序查找
  21. public HeroNode postOrderSearch(int id){
  22. if ( this.root != null){
  23. return this.root.postOrderSearch(id);
  24. }
  25. return null;
  26. }
  27. }

  
  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. HeroNode heroNode1 = new HeroNode( 1, "宋江"); // 根结点
  4. HeroNode heroNode2 = new HeroNode( 2, "吴用"); // 根节点的左子结点
  5. HeroNode heroNode3 = new HeroNode( 3, "武松"); // 根节点的右子结点
  6. HeroNode heroNode4 = new HeroNode( 4, "林冲"); // 根节点的右子结点的右子结点
  7. BinaryTree binaryTree = new BinaryTree(heroNode1);
  8. heroNode1.setLeft(heroNode2);
  9. heroNode1.setRight(heroNode3);
  10. heroNode3.setRight(heroNode4);
  11. // 查找
  12. System.out.println( "前序查找:");
  13. if (binaryTree.preOrderSearch( 2) != null){
  14. System.out.println( "找到了,"+binaryTree.preOrderSearch( 2)); // 前序查找
  15. } else {
  16. System.out.println( "没有找到");
  17. }
  18. System.out.println( "中序查找:");
  19. if (binaryTree.preOrderSearch( 2) != null){
  20. System.out.println( "找到了,"+binaryTree.infixOrderSearch( 2)); // 中序查找
  21. } else {
  22. System.out.println( "没有找到");
  23. }
  24. System.out.println( "后序查找:");
  25. if (binaryTree.preOrderSearch( 2) != null){
  26. System.out.println( "找到了,"+binaryTree.postOrderSearch( 2)); // 后序查找
  27. } else {
  28. System.out.println( "没有找到");
  29. }
  30. }
  31. }

如果想要比较这三种查找的优越性,可以在if(this.no = id)上面添加一个变量来判断它们各自递归了几次,这里就不演示了。注意的是一定是在if(this.no = id)上,因为不论怎么递归最终目的都是判断当前结点是否相等。

二叉树的删除:

这里的删除指的是递归删除结点,我们暂且规定,(1)如果删除的结点是叶子结点,那么删除该节点;(2)如果删除的结点不是叶子结点,那么删除该子树。(当然我们可以把子节点转为根节点,这里先不讨论那种情况)

满足上面两个条件的删除结点的思路:

  1. 首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可,否则递归删除。
  2. 因为二叉树是单向的,所以我们判断当前结点的子结点是否为需要删除的结点,而不能去判断当前结点是不是需要删除的结点。(这是因为,二叉树单向,所以子结点找不到父结点,比如我们要删除5号,我们找不到5号的父结点,没法置为null)
  3. 如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left = null,然后返回,递归结束,
  4. 如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right = null,然后返回,递归结束,
  5. 如果3和4没有删除掉,就需要向左子树递归删除,
  6. 如果5还没有删掉,就需要向右子树递归删除。


  
  1. class HeroNode{
  2. private int no;
  3. private String name;
  4. private HeroNode left; // 左结点
  5. private HeroNode right; // 右结点
  6. // 省略getter、setter和构造方法等
  7. /* 删除结点要求:(1)如果删除的结点是叶子结点,那么删除该节点;
  8. (2)如果删除的结点不是叶子结点,那么删除该子树。
  9. 0.首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可。
  10. 我们把0放到BinaryTree中 */
  11. public void delNode(int id){
  12. // 1.如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left=null,然后递归结束
  13. if ( this.left != null && this.left.no == id){
  14. this.left = null;
  15. return;
  16. }
  17. // 2.如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right=null,然后递归结束
  18. if ( this.right != null && this.right.no == id){
  19. this.right = null;
  20. return;
  21. }
  22. // 3.如果1和2没有删除掉,就需要向左子树递归删除
  23. if ( this.left != null){
  24. this.left.delNode(id);
  25. }
  26. // 4.如果3还没有删掉,就需要向右子树递归删除
  27. if ( this.right != null){
  28. this.right.delNode(id);
  29. }
  30. }
  31. }

  
  1. class BinaryTree{
  2. private HeroNode root; // 根结点
  3. public BinaryTree(HeroNode root){ // 指定根结点
  4. this.root = root;
  5. }
  6. // 删除结点
  7. public void delNode(int id){
  8. // 0.首先考虑,如果树是空树或者只有一个根节点root,直接将root置空即可,否则递归删除
  9. if ( this.root != null){
  10. if (root.getNo() == id){ //只有一个根节点root
  11. root = null;
  12. } else {
  13. root.delNode(id);
  14. }
  15. } else{
  16. System.out.println( "二叉树为空,无法删除");
  17. }
  18. }
  19. }

  
  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. HeroNode heroNode1 = new HeroNode( 1, "宋江"); // 根结点
  4. HeroNode heroNode2 = new HeroNode( 2, "吴用"); // 根节点的左子结点
  5. HeroNode heroNode3 = new HeroNode( 3, "武松"); // 根节点的右子结点
  6. HeroNode heroNode4 = new HeroNode( 4, "林冲"); // 根节点的右子结点的右子结点
  7. HeroNode heroNode5 = new HeroNode( 5, "李逵"); // 根节点的右子结点的右子结点
  8. BinaryTree binaryTree = new BinaryTree(heroNode1);
  9. heroNode1.setLeft(heroNode2);
  10. heroNode1.setRight(heroNode3);
  11. heroNode3.setLeft(heroNode4);
  12. heroNode3.setRight(heroNode5);
  13. int delNode = 2;
  14. System.out.println( "删除二叉树中的结点:"+delNode);
  15. binaryTree.delNode(delNode);
  16. System.out.println( "删除后的二叉树遍历:");
  17. binaryTree.preOrder();
  18. }
  19. }

 


转载:https://blog.csdn.net/weixin_43574446/article/details/105444451
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场