基于[二叉树的锯齿形层次遍历]对二叉树相关知识的归纳

基于[二叉树的锯齿形层次遍历问题]对二叉树系列知识的归纳

关于二叉树的基础知识

为什么使用二叉树?
树存储方式的分析 能提高数据存储,读取的效率, 比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也 可以保证数据的插入,删除,修改的速度。
定义BinaryTree 二叉树

class BinaryTree {
	private HeroNode root;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
}

前序遍历

前序遍历: 先输出父节点,再遍历左子树和右子树

//前序遍历
	public void preOrder() {
		if(this.root != null) {
			this.root.preOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
//前序遍历
	public HeroNode preOrderSearch(int no) {
		if(root != null) {
			return root.preOrderSearch(no);
		} else {
			return null;
		}
	}

中序遍历

中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

//中序遍历
	public void infixOrder() {
		if(this.root != null) {
			this.root.infixOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
//中序遍历
	public HeroNode infixOrderSearch(int no) {
		if(root != null) {
			return root.infixOrderSearch(no);
		}else {
			return null;
		}
	}

后序遍历

后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点

//后序遍历
public void postOrder() {
		if(this.root != null) {
			this.root.postOrder();
		}else {
			System.out.println("二叉树为空,无法遍历");
		}
	}
	//后序遍历
	public HeroNode postOrderSearch(int no) {
		if(root != null) {
			return this.root.postOrderSearch(no);
		}else {
			return null;
		}
	}

创建节点

//创建HeroNode节点(英雄人物)
class HeroNode {
   private int no;
   private String name;
   private HeroNode left; //默认null
   private HeroNode right; //默认null
   public HeroNode(int no, String name) {
      this.no = no;
      this.name = name;
   }
   public int getNo() {
      return no;
   }
   public void setNo(int no) {
      this.no = no;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public HeroNode getLeft() {
      return left;
   }
   public void setLeft(HeroNode left) {
      this.left = left;
   }
   public HeroNode getRight() {
      return right;
   }
   public void setRight(HeroNode right) {
      this.right = right;
   }
   @Override
   public String toString() {
      return "HeroNode [no=" + no + ", name=" + name + "]";
   }
}
   

查找与删除

查找

使用前序,中序,后序的方式来查询指定的结点

前序查找思路
1.先判断当前结点的no是否等于要查找的

2.如果是相等,则返回当前结点

3.如果不等,则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找

4.如果左递归前序查找,找到结点,则返回,否继续判断,当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找.

中序查找思路
1.判断当前结点的左子节点是否为空,如果不为空,则递归中序查找

2.如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点,否则继续进行右递归的中序查找

3.如果右递归中序查找,找到就返回,否则返回null

后序查找思路

1.判断当前结点的左子节点是否为空,如果不为空,则递归后序查找

2.如果找到,就返回,如果没有找到,就判断当前结点的右子节点是否为空,如果不为空,则右递归进行后序查找,如果找到,就返回

3.就和当前结点进行,比如,如果是则返回,否则返回null

二叉树-删除节点

要求

  1. 如果删除的节点是叶子节点,则删除该节点
  2. 如果删除的节点是非叶子节点,则删除该子树

思路

首先先处理:

考虑如果树是空树root,如果只有一个root结点,则等价将二叉树置空

然后进行下面步骤

1.因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是杂需要删除结点,而不能去判断当前这个结点是不是需要删除结点.

2.如果当前结点的左子结点不为空,并且左子结点就是要删除结点,就将this.left=null;并且就返回(结束递归删除)

3.如果当前结点的右子结点不为空,并且右子结点就是要删除结点,就将this.right=null;并且就返回(结束递归删除)

4.如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除

5.如果第4步也没有删除结点,则应当向右子树进行递归删除.

//递归删除结点
   //1.如果删除的节点是叶子节点,则删除该节点
   //2.如果删除的节点是非叶子节点,则删除该子树
   public void delNode(int no) {
      
      //思路
      /*
       *     1. 因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
         2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
         3. 如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
         4. 如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
         5.  如果第4步也没有删除结点,则应当向右子树进行递归删除.

       */
      //2. 如果当前结点的左子结点不为空,并且左子结点 就是要删除结点,就将this.left = null; 并且就返回(结束递归删除)
      if(this.left != null && this.left.no == no) {
         this.left = null;
         return;
      }
      //3.如果当前结点的右子结点不为空,并且右子结点 就是要删除结点,就将this.right= null ;并且就返回(结束递归删除)
      if(this.right != null && this.right.no == no) {
         this.right = null;
         return;
      }
      //4.我们就需要向左子树进行递归删除
      if(this.left != null) {
         this.left.delNode(no);
      }
      //5.则应当向右子树进行递归删除
      if(this.right != null) {
         this.right.delNode(no);
      }
   }
   
   //编写前序遍历的方法
   public void preOrder() {
      System.out.println(this); //先输出父结点
      //递归向左子树前序遍历
      if(this.left != null) {
         this.left.preOrder();
      }
      //递归向右子树前序遍历
      if(this.right != null) {
         this.right.preOrder();
      }
   }
   //中序遍历
   public void infixOrder() {
      
      //递归向左子树中序遍历
      if(this.left != null) {
         this.left.infixOrder();
      }
      //输出父结点
      System.out.println(this);
      //递归向右子树中序遍历
      if(this.right != null) {
         this.right.infixOrder();
      }
   }
   //后序遍历
   public void postOrder() {
      if(this.left != null) {
         this.left.postOrder();
      }
      if(this.right != null) {
         this.right.postOrder();
      }
      System.out.println(this);
   }
   
   //前序遍历查找
   /**
    * 
    * @param no 查找no
    * @return 如果找到就返回该Node ,如果没有找到返回 null
    */
   public HeroNode preOrderSearch(int no) {
      System.out.println("进入前序遍历");
      //比较当前结点是不是
      if(this.no == no) {
         return this;
      }
      //1.则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
      //2.如果左递归前序查找,找到结点,则返回
      HeroNode resNode = null;
      if(this.left != null) {
         resNode = this.left.preOrderSearch(no);
      }
      if(resNode != null) {//说明我们左子树找到
         return resNode;
      }
      //1.左递归前序查找,找到结点,则返回,否继续判断,
      //2.当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
      if(this.right != null) {
         resNode = this.right.preOrderSearch(no);
      }
      return resNode;
   }
   
   //中序遍历查找
   public HeroNode infixOrderSearch(int no) {
      //判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
      HeroNode resNode = null;
      if(this.left != null) {
         resNode = this.left.infixOrderSearch(no);
      }
      if(resNode != null) {
         return resNode;
      }
      System.out.println("进入中序查找");
      //如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
      if(this.no == no) {
         return this;
      }
      //否则继续进行右递归的中序查找
      if(this.right != null) {
         resNode = this.right.infixOrderSearch(no);
      }
      return resNode;
      
   }
   
   //后序遍历查找
   public HeroNode postOrderSearch(int no) {
      
      //判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
      HeroNode resNode = null;
      if(this.left != null) {
         resNode = this.left.postOrderSearch(no);
      }
      if(resNode != null) {//说明在左子树找到
         return resNode;
      }
      
      //如果左子树没有找到,则向右子树递归进行后序遍历查找
      if(this.right != null) {
         resNode = this.right.postOrderSearch(no);
      }
      if(resNode != null) {
         return resNode;
      }
      System.out.println("进入后序查找");
      //如果左右子树都没有找到,就比较当前结点是不是
      if(this.no == no) {
         return this;
      }
      return resNode;
   }
   
}

检验

//手动创建二叉树
public static void main(String[] args) {
   //先需要创建一颗二叉树
   BinaryTree binaryTree = new BinaryTree();
   //创建需要的结点
   HeroNode root = new HeroNode(1, "宋江");
   HeroNode node2 = new HeroNode(2, "吴用");
   HeroNode node3 = new HeroNode(3, "卢俊义");
   HeroNode node4 = new HeroNode(4, "林冲");
   HeroNode node5 = new HeroNode(5, "关胜");
   
   //说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树
   root.setLeft(node2);
   root.setRight(node3);
   node3.setRight(node4);
   node3.setLeft(node5);
   binaryTree.setRoot(root);
}

树结构实际应用

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复

杂度均为 O(nlogn),它也是不稳定排序。

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,

注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。

每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

堆排序基本思想

  1. 将待排序序列构造成一个大顶堆
  2. 此时,整个序列的最大值就是堆顶的根节点。
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序序列了。

平衡二叉树(AVL 树)

基本介绍
  1. 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。

  2. 具有以下特点:它是一 棵空树它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵

平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等

应用案例

左旋转

右旋转

双旋转

public class AVLTreeDemo {

   public static void main(String[] args) {
      //int[] arr = {4,3,6,5,7,8};
      //int[] arr = { 10, 12, 8, 9, 7, 6 };
      int[] arr = { 10, 11, 7, 6, 8, 9 };  
      //创建一个 AVLTree对象
      AVLTree avlTree = new AVLTree();
      //添加结点
      for(int i=0; i < arr.length; i++) {
         avlTree.add(new Node(arr[i]));
      }
      
      //遍历
      System.out.println("中序遍历");
      avlTree.infixOrder();
      
      System.out.println("在平衡处理~~");
      System.out.println("树的高度=" + avlTree.getRoot().height()); //3
      System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
      System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
      System.out.println("当前的根结点=" + avlTree.getRoot());//8
      
      
   }

}

// 创建AVLTree
class AVLTree {
   private Node root;

   public Node getRoot() {
      return root;
   }

   // 查找要删除的结点
   public Node search(int value) {
      if (root == null) {
         return null;
      } else {
         return root.search(value);
      }
   }

   // 查找父结点
   public Node searchParent(int value) {
      if (root == null) {
         return null;
      } else {
         return root.searchParent(value);
      }
   }

   // 编写方法:
   // 1. 返回的 以node 为根结点的二叉排序树的最小结点的值
   // 2. 删除node 为根结点的二叉排序树的最小结点
   /**
    * 
    * @param node
    *            传入的结点(当做二叉排序树的根结点)
    * @return 返回的 以node 为根结点的二叉排序树的最小结点的值
    */
   public int delRightTreeMin(Node node) {
      Node target = node;
      // 循环的查找左子节点,就会找到最小值
      while (target.left != null) {
         target = target.left;
      }
      // 这时 target就指向了最小结点
      // 删除最小结点
      delNode(target.value);
      return target.value;
   }

   // 删除结点
   public void delNode(int value) {
      if (root == null) {
         return;
      } else {
         // 1.需求先去找到要删除的结点 targetNode
         Node targetNode = search(value);
         // 如果没有找到要删除的结点
         if (targetNode == null) {
            return;
         }
         // 如果我们发现当前这颗二叉排序树只有一个结点
         if (root.left == null && root.right == null) {
            root = null;
            return;
         }

         // 去找到targetNode的父结点
         Node parent = searchParent(value);
         // 如果要删除的结点是叶子结点
         if (targetNode.left == null && targetNode.right == null) {
            // 判断targetNode 是父结点的左子结点,还是右子结点
            if (parent.left != null && parent.left.value == value) { // 是左子结点
               parent.left = null;
            } else if (parent.right != null && parent.right.value == value) {// 是由子结点
               parent.right = null;
            }
         } else if (targetNode.left != null && targetNode.right != null) { // 删除有两颗子树的节点
            int minVal = delRightTreeMin(targetNode.right);
            targetNode.value = minVal;

         } else { // 删除只有一颗子树的结点
            // 如果要删除的结点有左子结点
            if (targetNode.left != null) {
               if (parent != null) {
                  // 如果 targetNode 是 parent 的左子结点
                  if (parent.left.value == value) {
                     parent.left = targetNode.left;
                  } else { // targetNode 是 parent 的右子结点
                     parent.right = targetNode.left;
                  }
               } else {
                  root = targetNode.left;
               }
            } else { // 如果要删除的结点有右子结点
               if (parent != null) {
                  // 如果 targetNode 是 parent 的左子结点
                  if (parent.left.value == value) {
                     parent.left = targetNode.right;
                  } else { // 如果 targetNode 是 parent 的右子结点
                     parent.right = targetNode.right;
                  }
               } else {
                  root = targetNode.right;
               }
            }

         }

      }
   }

   // 添加结点的方法
   public void add(Node node) {
      if (root == null) {
         root = node;// 如果root为空则直接让root指向node
      } else {
         root.add(node);
      }
   }

   // 中序遍历
   public void infixOrder() {
      if (root != null) {
         root.infixOrder();
      } else {
         System.out.println("二叉排序树为空,不能遍历");
      }
   }
}

// 创建Node结点
class Node {
   int value;
   Node left;
   Node right;

   public Node(int value) {

      this.value = value;
   }

   // 返回左子树的高度
   public int leftHeight() {
      if (left == null) {
         return 0;
      }
      return left.height();
   }

   // 返回右子树的高度
   public int rightHeight() {
      if (right == null) {
         return 0;
      }
      return right.height();
   }

   // 返回 以该结点为根结点的树的高度
   public int height() {
      return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
   }
   
   //左旋转方法
   private void leftRotate() {
      
      //创建新的结点,以当前根结点的值
      Node newNode = new Node(value);
      //把新的结点的左子树设置成当前结点的左子树
      newNode.left = left;
      //把新的结点的右子树设置成带你过去结点的右子树的左子树
      newNode.right = right.left;
      //把当前结点的值替换成右子结点的值
      value = right.value;
      //把当前结点的右子树设置成当前结点右子树的右子树
      right = right.right;
      //把当前结点的左子树(左子结点)设置成新的结点
      left = newNode;
      
      
   }
   
   //右旋转
   private void rightRotate() {
      Node newNode = new Node(value);
      newNode.right = right;
      newNode.left = left.right;
      value = left.value;
      left = left.left;
      right = newNode;
   }

   // 查找要删除的结点
   /**
    * 
    * @param value
    *            希望删除的结点的值
    * @return 如果找到返回该结点,否则返回null
    */
   public Node search(int value) {
      if (value == this.value) { // 找到就是该结点
         return this;
      } else if (value < this.value) {// 如果查找的值小于当前结点,向左子树递归查找
         // 如果左子结点为空
         if (this.left == null) {
            return null;
         }
         return this.left.search(value);
      } else { // 如果查找的值不小于当前结点,向右子树递归查找
         if (this.right == null) {
            return null;
         }
         return this.right.search(value);
      }

   }

   // 查找要删除结点的父结点
   /**
    * 
    * @param value
    *            要找到的结点的值
    * @return 返回的是要删除的结点的父结点,如果没有就返回null
    */
   public Node searchParent(int value) {
      // 如果当前结点就是要删除的结点的父结点,就返回
      if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
         return this;
      } else {
         // 如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
         if (value < this.value && this.left != null) {
            return this.left.searchParent(value); // 向左子树递归查找
         } else if (value >= this.value && this.right != null) {
            return this.right.searchParent(value); // 向右子树递归查找
         } else {
            return null; // 没有找到父结点
         }
      }

   }

   @Override
   public String toString() {
      return "Node [value=" + value + "]";
   }

   // 添加结点的方法
   // 递归的形式添加结点,注意需要满足二叉排序树的要求
   public void add(Node node) {
      if (node == null) {
         return;
      }

      // 判断传入的结点的值,和当前子树的根结点的值关系
      if (node.value < this.value) {
         // 如果当前结点左子结点为null
         if (this.left == null) {
            this.left = node;
         } else {
            // 递归的向左子树添加
            this.left.add(node);
         }
      } else { // 添加的结点的值大于 当前结点的值
         if (this.right == null) {
            this.right = node;
         } else {
            // 递归的向右子树添加
            this.right.add(node);
         }

      }
      
      //当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
      if(rightHeight() - leftHeight() > 1) {
         //如果它的右子树的左子树的高度大于它的右子树的右子树的高度
         if(right != null && right.leftHeight() > right.rightHeight()) {
            //先对右子结点进行右旋转
            right.rightRotate();
            //然后在对当前结点进行左旋转
            leftRotate(); //左旋转..
         } else {
            //直接进行左旋转即可
            leftRotate();
         }
         return ; //必须要!!!
      }
      
      //当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
      if(leftHeight() - rightHeight() > 1) {
         //如果它的左子树的右子树高度大于它的左子树的高度
         if(left != null && left.rightHeight() > left.leftHeight()) {
            //先对当前结点的左结点(左子树)->左旋转
            left.leftRotate();
            //再对当前结点进行右旋转
            rightRotate();
         } else {
            //直接进行右旋转即可
            rightRotate();
         }
      }
   }

   // 中序遍历
   public void infixOrder() {
      if (this.left != null) {
         this.left.infixOrder();
      }
      System.out.println(this);
      if (this.right != null) {
         this.right.infixOrder();
      }
   }

}

二叉树的问题分析

  1. 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如 1 亿), 就

存在如下问题:

  1. 问题 1:在构建二叉树时,需要多次进行 i/o 操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,

速度有影响

  1. 问题 2:节点海量,也会造成二叉树的高度很大,会降低操作速度
多叉树——B 树的基本介绍

B 树通过重新组织节点,降低树的高度,并且减少 i/o 读写次数来提升效率。

  1. B 树通过重新组织节点, 降低了树的高度.

  2. 文件系统及数据库系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页(页得大小通常为 4k), 这样每个节点只需要一次 I/O 就可以完全载入

  3. 将树的度 M 设置为 1024,在 600 亿个元素中最多只需要 4 次 I/O 操作就可以读取到想要的元素, B 树(B+)广泛

应用于文件存储系统以及数据库系统中

图基本介绍

为什么要有图

  1. 前面我们学了线性表和树
  2. 线性表局限于一个直接前驱和一个直接后继的关系
  3. 树也只能有一个直接前驱也就是父节点
  4. 当我们需要表示多对多的关系时, 这里我们就用到了

图——深度优先遍历(DFS)

图的深度优先搜索(Depth First Search) 。

  1. 深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点, 可以这样理解: 每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
  2. 我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
  3. 显然,深度优先搜索是一个递归的过程

深度优先遍历算法步骤

  1. 访问初始结点 v,并标记结点 v 为已访问。
  2. 查找结点 v 的第一个邻接结点 w。
  3. 若 w 存在,则继续执行 4,如果 w 不存在,则回到第 1 步,将从 v 的下一个结点继续。
  4. 若 w 未被访问,对 w 进行深度优先遍历递归(即把 w 当做另一个 v,然后进行步骤 123)。
  5. 查找结点 v 的 w 邻接结点的下一个邻接结点,转到步骤 3。
  6. 分析图
//深度优先遍历算法
//i 第一次就是 0
private void dfs(boolean[] isVisited, int i) {
   //首先我们访问该结点,输出
   System.out.print(getValueByIndex(i) + "->");
   //将结点设置为已经访问
   isVisited[i] = true;
   //查找结点i的第一个邻接结点w
   int w = getFirstNeighbor(i);
   while(w != -1) {//说明有
      if(!isVisited[w]) {
         dfs(isVisited, w);
      }
      //如果w结点已经被访问过
      w = getNextNeighbor(i, w);
   }
   
}
//对dfs 进行一个重载, 遍历我们所有的结点,并进行 dfs
public void dfs() {
   isVisited = new boolean[vertexList.size()];
   //遍历所有的结点,进行dfs[回溯]
   for(int i = 0; i < getNumOfVertex(); i++) {
      if(!isVisited[i]) {
         dfs(isVisited, i);
      }
   }
}

图——广度优先遍历(BFS)

广度优先遍历基本思想

  1. 图的广度优先搜索(Broad First Search) 。
  2. 类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点

广度优先遍历算法步骤

  1. 访问初始结点 v 并标记结点 v 为已访问。
  2. 结点 v 入队列尚硅谷 Java 数据结构和算法
  3. 当队列非空时,继续执行,否则算法结束。
  4. 出队列,取得队头结点 u。
  5. 查找结点 u 的第一个邻接结点 w。
  6. 若结点 u 的邻接结点 w 不存在,则转到步骤 3;否则循环执行以下三个步骤:
  • 若结点 w 尚未被访问,则访问结点 w 并标记为已访问。
  • 结点 w 入队列
  • 查找结点 u 的继 w 邻接结点后的下一个邻接结点 w,转到步骤 6。
//对一个结点进行广度优先遍历的方法
private void bfs(boolean[] isVisited, int i) {
   int u ; // 表示队列的头结点对应下标
   int w ; // 邻接结点w
   //队列,记录结点访问的顺序
   LinkedList queue = new LinkedList();
   //访问结点,输出结点信息
   System.out.print(getValueByIndex(i) + "=>");
   //标记为已访问
   isVisited[i] = true;
   //将结点加入队列
   queue.addLast(i);
   
   while( !queue.isEmpty()) {
      //取出队列的头结点下标
      u = (Integer)queue.removeFirst();
      //得到第一个邻接结点的下标 w 
      w = getFirstNeighbor(u);
      while(w != -1) {//找到
         //是否访问过
         if(!isVisited[w]) {
            System.out.print(getValueByIndex(w) + "=>");
            //标记已经访问
            isVisited[w] = true;
            //入队
            queue.addLast(w);
         }
         //以u为前驱点,找w后面的下一个邻结点
         w = getNextNeighbor(u, w); //体现出我们的广度优先
      }
   }
   
} 

//遍历所有的结点,都进行广度优先搜索
public void bfs() {
   isVisited = new boolean[vertexList.size()];
   for(int i = 0; i < getNumOfVertex(); i++) {
      if(!isVisited[i]) {
         bfs(isVisited, i);
      }
   }
}

图中常用方法

//图中常用的方法
//返回结点的个数
public int getNumOfVertex() {
   return vertexList.size();
}
//显示图对应的矩阵
public void showGraph() {
   for(int[] link : edges) {
      System.err.println(Arrays.toString(link));
   }
}
//得到边的数目
public int getNumOfEdges() {
   return numOfEdges;
}
//返回结点i(下标)对应的数据 0->"A" 1->"B" 2->"C"
public String getValueByIndex(int i) {
   return vertexList.get(i);
}
//返回v1和v2的权值
public int getWeight(int v1, int v2) {
   return edges[v1][v2];
}
//插入结点
public void insertVertex(String vertex) {
   vertexList.add(vertex);
}
//添加边
/**
 * 
 * @param v1 表示点的下标即使第几个顶点  "A"-"B" "A"->0 "B"->1
 * @param v2 第二个顶点对应的下标
 * @param weight 表示 
 */
public void insertEdge(int v1, int v2, int weight) {
   edges[v1][v2] = weight;
   edges[v2][v1] = weight;
   numOfEdges++;
}

深度VS广度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ts8V5MIL-1616296000701)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210321105920780.png)]

1)深度优先遍历顺序为1->2->4->8->5->3->6->7
2)广度优先算法的遍历顺序为:1->2->3->4->5->6->7->8

解题过程

题目描述

给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

例如:

给定二叉树 [3,9,20,null,null,15,7],

3

/ \

9 20

/ \

15 7

返回锯齿形层序遍历如下:

[

[3],

[20,9],

[15,7]

]

方法一:广度优先遍历

非递归的代码:

public static void levelOrder(TreeNode tree) {
    if (tree == null)
        return;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(tree);//相当于把数据加入到队列尾部
    while (!queue.isEmpty()) {
        //poll方法相当于移除队列头部的元素
        TreeNode node = queue.poll();
        System.out.println(node.val);
        if (node.left != null)
            queue.add(node.left);
        if (node.right != null)
            queue.add(node.right);
    }
}

因为上面每次都是从左边开始打印,但这题要求的是先从左边,下一层从右边,在下一次从左边……左右交替的。我们可以使用一个变量leftToRight,如果是true就表示从左边开始打印,否则就从右边开始打印,只需要把上面代码修改下即可,来看下

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if (root == null)
        return res;
    //创建队列,保存节点
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);//先把节点加入到队列中
    boolean leftToRight = true;//第一步先从左边开始打印
    while (!queue.isEmpty()) {
        //记录每层节点的值
        List<Integer> level = new ArrayList<>();
        //统计这一层有多少个节点
        int count = queue.size();
        //遍历这一层的所有节点,把他们全部从队列中移出来,顺便
        //把他们的值加入到集合level中,接着再把他们的子节点(如果有)
        //加入到队列中
        for (int i = 0; i < count; i++) {
            //poll移除队列头部元素(队列在头部移除,尾部添加)
            TreeNode node = queue.poll();
            //判断是从左往右打印还是从右往左打印。
            if (leftToRight) {
                //如果从左边打印,直接把访问的节点值加入到列表level的末尾即可
                level.add(node.val);
            } else {
                //如果是从右边开始打印,每次要把访问的节点值
                //加入到列表的最前面
                level.add(0, node.val);
            }
            //左右子节点如果不为空会被加入到队列中
            if (node.left != null)
                queue.add(node.left);
            if (node.right != null)
                      }
            //把这一层的节点值加入到集合res中
            res.add(level);
            //改变下次访问的方向
            leftToRight = !leftToRight;
        }
        return res;
    }

方法二:深度优先遍历

这题除了使用BFS以外,还可以使用DFS,也可以参照373,数据结构-6,树中二叉树的BFS遍历的递归解法,稍作修改。但这里要有个判断,如果走到下一层的时候集合没有创建,要先创建下一层的集合,代码也很简单,我们来看下。

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    travel(root, res, 0);
    return res;
}

private void travel(TreeNode cur, List<List<Integer>> res, int level) {
    if (cur == null)
        return;
    //如果res.size() <= level说明下一层的集合还没创建,所以要先创建下一层的集合
    if (res.size() <= level) {
        List<Integer> newLevel = new LinkedList<>();
        res.add(newLevel);
    }
    //遍历到第几层我们就操作第几层的数据
    List<Integer> list = res.get(level);
    //这里默认根节点是第0层,偶数层相当于从左往右遍历,
    // 所以要添加到集合的末尾,如果是奇数层相当于从右往左遍历,
    // 要把数据添加到集合的开头
    if (level % 2 == 0)
        list.add(cur.val);
    else
        list.add(0, cur.val);
    //分别遍历左右两个子节点,到下一层了,所以层数要加1
    travel(cur.left, res, level + 1);
    travel(cur.right, res, level + 1);
}

官方方法

import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * @author Zhanglu
 * @date 2021/3/21 - 11:39
 */
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {
    }

    TreeNode(int val) {
        this.val = val;
    }

    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
public class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    // 创建双向链表,按题意输出结果->需List<List<Integer>>
    List<List<Integer>> ans = new LinkedList<List<Integer>>();
    // 判断是否有节点
    if(root == null){
        return ans;
    }
    // 实现Queue类双向链表作为队列使用(FIFO)
    Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
    // 队列中加入根结点
    nodeQueue.offer(root);
    boolean isOrderLeft = true; // 默认有左子结点

    while (!nodeQueue.isEmpty()) {
        // 实现Deque作为`双端队列`使用->因为`双端队列`可以在首尾插入或删除元素
        Deque<Integer> levelList = new LinkedList<Integer>();
        // 计算队列长度
        int size = nodeQueue.size();
        // 遍历队列
        for(int i = 0; i < size; ++i) {
            // 从队列中推出节点作为当前结点(FIFO)
            TreeNode curNode = nodeQueue.poll();
            // 利用Deque双端队列性质
            // 若有左子节点,offerLast添加结点到尾部
            // 若有右子节点,offerFirst添加结点到头部
            if(isOrderLeft){
                levelList.offerLast(curNode.val);
            } else {
                levelList.offerFirst(curNode.val);
            }
            // 遍历当前节点的左、右子节点,若有节点添加进nodeQueue
            if(curNode.left != null) {
                nodeQueue.offer(curNode.left);
            }
            if(curNode.right != null) {
                nodeQueue.offer(curNode.right);
            }
        }
        // 数组中添加遍历得到的队列(使用新的LinkedList存储)
        ans.add(new LinkedList<Integer>(levelList));
        isOrderLeft = !isOrderLeft;
    }
    // 返回结点
    return ans;
}
}

巨人的肩膀:

1.尚硅谷——java数据结构与算法

2.力扣(LeetCode)sdwwld https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/solution/bfshe-dfsliang-chong-jie-jue-fang-shi-by-184y/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值