二叉树的详解

目录

二叉树的遍历

     递归遍历

     非递归遍历

如何直观打印一棵树 

宽度优先遍历

     介绍及实现

     实际应用 

二叉树的相关概念及其实现判断 

     如何判断一棵二叉树是否是搜索二叉树?

     如果判断一棵二叉树是否为完全二叉树?

 二叉树的递归套路的应用

     如何判断一棵树是否为平衡二叉树?

     如何判断一棵二叉树是否是搜索二叉树?

      如何判断一棵树是否是满二叉树?

二叉树的应用

     寻找最低公共祖先       

     找一个节点的后继节点

     二叉树的序列化和反序列化 

     折纸问题

 

二叉树的遍历

     递归遍历

先序遍历:对于二叉树先打印头节点,然后打印左子树,右子树。

中序遍历:对于二叉树先打印左子树,然后打印头节点,右子树。

后序遍历:对于二叉树先打印左子树,然后打印右子树,头节点。

       而对于这三个顺序的遍历,可以通过递归顺序遍历二叉树取相应的节点得到。先使用递归遍历完二叉树所有的节点,每一个节点被访问三次,对于先序遍历就是依次取出第一次访问的节点,中序遍历就是依次取出第二次访问的节点,后序遍历就是依次取出第三次访问的节点。

public static void preOrderRecur(Node head) {//先序遍历
	if (head == null) {
		return;
	}
	System.out.print(head.value + " ");
	preOrderRecur(head.left);
	preOrderRecur(head.right);
}

public static void inOrderRecur(Node head) {//中序遍历
	if (head == null) {
		return;
	}
	inOrderRecur(head.left);
	System.out.print(head.value + " ");
	inOrderRecur(head.right);
}

public static void posOrderRecur(Node head) {//后序遍历
	if (head == null) {
		return;
	}
	posOrderRecur(head.left);
	posOrderRecur(head.right);
	System.out.print(head.value + " ");
}

      非递归遍历

先序遍历:使用栈进行非递归遍历,先将二叉树头节点压入栈中,然后从栈中弹出一个节点,打印,然后先右子树,再左子树(如果有,没有的话直接跳过这个步骤),然后重复上述操作,完成先序遍历。

public static void preOrderUnRecur(Node head) {//先序遍历
	System.out.print("pre-order: ");
	if (head != null) {
		Stack<Node> stack = new Stack<Node>();
		stack.add(head);
		while (!stack.isEmpty()) {
			head = stack.pop();
			System.out.print(head.value + " ");
			if (head.right != null) {
				stack.push(head.right);
			}
			if (head.left != null) {
				stack.push(head.left);
			}
		}
	}
	System.out.println();
}

后序遍历:准备两个栈,先将头节点压入第一个栈,然后弹出放入另一个栈,接着先左子树,后右子树继续同样的操作,执行下去,直到最后,最后把另一个栈的节点全部出栈,打印出来就是后序遍历。(因为压入第一个栈的顺序是头左右,那么到另一个栈的顺序就是头右左,再全部出栈打印出来就是左右头)

public static void posOrderUnRecur1(Node head) {//后续遍历
	System.out.print("pos-order: ");
	if (head != null) {
           //准备两个栈
	Stack<Node> s1 = new Stack<Node>();
	Stack<Node> s2 = new Stack<Node>();
		s1.push(head);
		while (!s1.isEmpty()) {
			head = s1.pop();
			s2.push(head);
			if (head.left != null) {
				s1.push(head.left);
			}
			if (head.right != null) {
				s1.push(head.right);
			}
		}
		while (!s2.isEmpty()) {
			System.out.print(s2.pop().value + " ");
		}
	}
	System.out.println();
}

中序遍历:每棵子树整棵树左边界进栈,依次弹出节点打印,同时对弹出节点的右树执行上述操作

public static void inOrderUnRecur(Node head) {//中序遍历
	System.out.print("in-order: ");
	if (head != null) {
	Stack<Node> stack = new Stack<Node>();
	  while (!stack.isEmpty() || head != null) {
		  if (head != null) {
			  stack.push(head);
			  head = head.left;
		  } else {
			  head = stack.pop();
			  System.out.print(head.value + " ");
			  head = head.right;
		  }
	  }
  }
  System.out.println();
}

     如何直观打印一棵树 

        这个方法可以用来直观查看自己所做的树的形状,实现的代码如下:

public class Code_PrintBinaryTree {

	public static class Node {
		public int value;
		public Node left;
		public Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static void printTree(Node head) {
		System.out.println("Binary Tree:");
		printInOrder(head, 0, "H", 17);
		System.out.println();
	}

	public static void printInOrder(Node head, int height, String to, int len) {
		if (head == null) {
			return;
		}
		printInOrder(head.right, height + 1, "v", len);
		String val = to + head.value + to;
		int lenM = val.length();
		int lenL = (len - lenM) / 2;
		int lenR = len - lenM - lenL;
		val = getSpace(lenL) + val + getSpace(lenR);
		System.out.println(getSpace(height * len) + val);
		printInOrder(head.left, height + 1, "^", len);
	}

	public static String getSpace(int num) {
		String space = " ";
		StringBuffer buf = new StringBuffer("");
		for (int i = 0; i < num; i++) {
			buf.append(space);
		}
		return buf.toString();
	}

	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(-222222222);
		head.right = new Node(3);
		head.left.left = new Node(Integer.MIN_VALUE);
		head.right.left = new Node(55555555);
		head.right.right = new Node(66);
		head.left.left.right = new Node(777);
		printTree(head);

		head = new Node(1);
		head.left = new Node(2);
		head.right = new Node(3);
		head.left.left = new Node(4);
		head.right.left = new Node(5);
		head.right.right = new Node(6);
		head.left.left.right = new Node(7);
		printTree(head);

		head = new Node(1);
		head.left = new Node(1);
		head.right = new Node(1);
		head.left.left = new Node(1);
		head.right.left = new Node(1);
		head.right.right = new Node(1);
		head.left.left.right = new Node(1);
		printTree(head);

	}

}

宽度优先遍历

     介绍及实现

       二叉树的先序遍历就是二叉树的深度优先遍历,而宽度优先遍历采用的是队列实现,先将头节点放入队列,然后弹出打印,接着从上到下,先左子树后右子树,进入队列,然后弹出打印,重复操作。

public static void w(Node head){
       if(head == null){
           return;
       }
       Queue<Node> queue = new LinkedList<>();
       queue.add(head);
       while(!queue.isEmpty()){
           Node cur = queue.poll();
           System.out.println(cur.value);
           if(cur.left!= null){
                queue.add(cur.left);
           }
           if(cur.right!=null){
                queue.add(cur.right);
           }
        }
}

     实际应用 

       求一棵二叉树的最大宽度?

       采用哈希表进行解决,记录每一层的层数和节点数,得出最大的宽度

//使用哈希表的方式解决,记录每一层的层数和节点数
public static void w(Node head){
        if(head == null){
            return 0;
        }
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        HashMap<Node,Integer> levelMap = new HashMap<>();//设置一个哈希表,用来存放当前节点的层数
        levelMap.put(head,1);//先将头节点放进去作为第一层
        int curlevel = 1;//当前的层数
        int curLevelNodes = 0;//当前层的节点数,因为head只是进去了还没有弹出
        int max = Integer.MIN_VALUE;//全局最大层数的节点数
        while(!queue.isEmpty()){
            Node cur = queue.poll();//从队列弹出一个节点
            int curNodeLevel = levelMap.get(cur);//当前节点的层数
            if(curNodeLevel == curLevel){//如果当前节点的层数和当前层数相等,当前层数的节点数增加
                  curLevelNodes++;
             }
             else{
                max = Math.max(max,curLevelNodes);
                curLevel++;
                curLevelNodes = 1;
             }
          
            if(cur.left!= null){
                 levelMap.put(cur.left,curNodeLevel+1);//左子树进入时当前节点的层数+1,作为当前层数
                 queue.add(cur.left);
            }
            if(cur.right!=null){
                 levelMap.put(cur.right,curNodeLevel+1);
                 queue.add(cur.right);
            }
         }
         return max;
}

二叉树的相关概念及其实现判断 

     如何判断一棵二叉树是否是搜索二叉树?

       搜索二叉树指的是每一棵子树,左子树都比根节点小,右子树都比根节点大。搜索二叉树的判断,采用中序遍历的方式,如果将一棵二叉树采用中序遍历得到的节点是升序的,那么它就是一棵搜索二叉树。只需要在中序遍历的基础上进行一些判断即可,递归与非递归的实现方式分别如下:

//递归方式判断
public static int preValue = Integer.MIN_VALUE;

public static boolean checkBST(Node head) {
	if (head == null) {
		return true;//如果树为空,那么它是一棵搜索二叉树,直接返回。
	}
    boolean isLeftBst = checkBST(head.left);//先检查左子树
    if(!isLeftBst){
        return false;
    }
    if(head.value <= preValue){//将左子树上的最后一个值和当前最小值进行比较
         return false;
    }
    else{
         preValue = head.value;//将左子树的最后一个值作为当前的最小值
    }
       
    return checkBST(head.right);//检查右子树
}
//非递归方式判断
public static void checkBST(Node head) {
		
	if (head != null) {
        int preValue = Integer.MIN_VALUE;
		Stack<Node> stack = new Stack<Node>();
		while (!stack.isEmpty() || head != null) {
			if (head != null) {
				stack.push(head);
				head = head.left;
			} else {
				head = stack.pop();
				if(head.value <= preValue){
                       return false;
                }
                else{
                    preValue = head.value;
                }
                    
			    head = head.right;
			}
		}
	}
	return true;
}

     如果判断一棵二叉树是否为完全二叉树?

       完全二叉树指的是一棵树每一层从左到右依次变满。对二叉树采用宽度优先遍历的方式进行遍历,同时进行判断,对于任意一个节点,如果只有右子树没有左子树,那么它一定不是完全二叉树,在上述满足的基础上,如果遇到了第一个左右子树不全的节点,那么它后面的节点都是叶节点。

public static boolean isCBT(Node head) {
	if (head == null) {
		return true;
	}
	LinkedList<Node> queue = new LinkedList<>();
	boolean leaf = false;//表示是否遇到左右孩子不双全的节点
	Node l = null;
	Node r = null;
	queue.add(head);//头节点进入队列
	while (!queue.isEmpty()) {
		head = queue.poll();
		l = head.left;//左孩子
		r = head.right;//右孩子
		if ((leaf && (l != null || r != null)) //如果遇到了不双全节点后,又发现当前节点居然有孩子
            || (l == null && r != null)) {
			return false;
		}
		if (l != null) {
			queue.add(l);
		}
		if (r != null) {
			queue.add(r);
		} 
        if(l ==null || r==null){
			leaf = true;
		}
	}
	return true;
}

 二叉树的递归套路的应用

     如何判断一棵树是否为平衡二叉树?

       平衡二叉树指的是左右两棵子树的深度相差的绝对值小于等于1。这个问题的解决采用的是二叉树的递归套路,这个套路的实现首先你要明白你需要从左右两棵子树得到什么信息,判断一棵树是否为平衡二叉树需要从左右子树得到信息为,左子树是否为平衡二叉树,右子树是否为平衡二叉树,以及两棵子树分别的深度。最后对于整体的自身,也需要同样的信息作为返回,这样才能成功的将递归连起来。具体实现的代码如下:

    public static boolean isBalanced(Node head) {
		return process(head).isBalanced;
	}

	public static class ReturnType {
		public boolean isBalanced;//是否是平衡二叉树
		public int height;//深度

		public ReturnType(boolean isB, int hei) {
			isBalanced = isB;
			height = hei;
		}
	}

	public static ReturnType process(Node x) {
		if (x == null) {
			return new ReturnType(true, 0);
		}
		ReturnType leftData = process(x.left);
		ReturnType rightData = process(x.right);
		int height = Math.max(leftData.height, rightData.height)+1;
		boolean isBalanced = leftData.isBalanced && rightData.isBalanced
				&& Math.abs(leftData.height - rightData.height) < 2;
		return new ReturnType(isBalanced, height);
	}

     如何判断一棵二叉树是否是搜索二叉树?

       对于这个问题同样可以采用上面的递归套路的方式进行求解,首先进行分析,我需要从左右两棵子树得到什么信息,我需要得到的是左子树是否为搜索二叉树,左子树的最大值,右子树是否为搜索二叉树,右子树的最小值,但是由于采用递归的方式实现要求结构相同,所以统一需要左右两棵子树是否为搜索二叉树,以及最大值和最小值的信息。最后对于整体的自身,也需要同样的信息作为返回,这样才能成功的将递归连起来。

public static class ReturnData{
     public boolean isBST;
     public int min;
     public int max;
     public ReturnData(boolean is,int mi,int ma){
           isBST = is;
           min = mi;
           max = ma;
     }
}
public static ReturnData process(Node x){
       if(x == null){
           return null;
        }
        ReturnData leftData = process(x.left);
        ReturnData rightData = process(x,right);
        int min = x.value;
        int max = x.value;
        if(leftData!=null){
             min = Math.min(min,leftData.min);
             max = Math.max(max,leftData.max);
         }
         if(rightData!=null){
             min = Math.min(min,rightData.min);
             max = Math.max(max,rightData.max);
         }
         boolean isBST = true;
         if(leftData!=null&&(!leftData.isBST  || leftData.max >=x.value)){
                isBST = false;
         }
         if(rightData!=null&&(!rightData.isBST  || rightData.min<=x.value)){
                 isBST = false;
         }
         return new ReturnData(isBST,min,max);
}

      如何判断一棵树是否是满二叉树?

        对于这个问题,同样采用递归套路的方式轻松解决。

public static boolean isF(Node head){
     if(head == null){
        return true;
     }
     Info data = f(head);
     return data.nodes == (1<<data.height - 1);
}
public static class Info{
     public int height;
     public int nodes;
     public Info(int h,int n){
         height = h;
         nodes = n;
     }
}
public static Info f(Node x){
     if(x == null){
         return new Info(0,0);
      }
     Info leftData = f(x.left);
     Info rightData = f(x.right);
     int height = Math.max(leftData.height,rightData.height)+1;
     int nodes = leftData.nodes + rightData.nodes+1;
     return new Info(height,nodes);
}
    

       上面三个问题都可以采用递归套路去解决,递归套路主要用于解决二叉树中从左右两棵子树中得到信息去求解,然后从每一棵子树中得到的信息是相同的。(这一类问题其实也就是树形DP问题)

二叉树的应用

     寻找最低公共祖先       

       给定两个二叉树的节点node1和node2,找到它们的最低公共祖先节点(指的是两个节点往后上走,第一个汇聚的点)

        这个问题分为两种情况,第一种就是node1是node2的最低公共祖先节点,或者node2是node1的最低公共祖先;第二种情况就是node1与node2不互为公共祖先。

   public static Node lowestAncestor(Node head, Node o1, Node o2) {
		if (head == null || head == o1 || head == o2) {
			return head;
		}
		Node left = lowestAncestor(head.left, o1, o2);
		Node right = lowestAncestor(head.right, o1, o2);
		if (left != null && right != null) {
			return head;
		}
        //左右两棵树,并不都有返回值
		return left != null ? left : right;
	}

     找一个节点的后继节点

       现在有一种新的二叉树节点类型如下:

public class Node{
    public int value;
    public Node left;
    public Node right;
    public Node parent;
    public Node(int val){
        value = val;
    }
}
   

       该结构比普通二叉树节点结构多了一个指向父节点的parent指针。 假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。 只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。 在二叉树的中序遍历的序列中, node的下一个节点叫作node的后继节点。

        这个问题有两种情况:如果node有右子树,那么它的后继节点就是它的右子树上的最左节点;如果node没有右子树,向上寻找直到向上寻找的节点是它的父节点的左孩子,那么这个父节点就是node的后继节点,但是如果一直到最后都没有找到,那么就是null。

public static Node getSuccessorNode(Node node) {
		if (node == null) {
			return node;
		}
		if (node.right != null) {//如果node有右子树,找到它的最左的孩子
			return getLeftMost(node.right);
		} else {//无右子树
			Node parent = node.parent;
			while (parent != null && parent.left != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	public static Node getLeftMost(Node node) {
		if (node == null) {
			return node;
		}
		while (node.left != null) {
			node = node.left;
		}
		return node;
	}

     二叉树的序列化和反序列化 

       就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

  //序列化,这里采用先序方式
  public static String serialByPre(Node head) {
		if (head == null) {
			return "#!";
		}
		String res = head.value + "!";
		res += serialByPre(head.left);
		res += serialByPre(head.right);
		return res;
	}
  //先序方式反序列化
  public static Node reconByPreString(String preStr) {//先将值分割出来放进队列中
		String[] values = preStr.split("!");
		Queue<String> queue = new LinkedList<String>();
		for (int i = 0; i != values.length; i++) {
			queue.offer(values[i]);
		}
		return reconPreOrder(queue);
	}

	public static Node reconPreOrder(Queue<String> queue) {
		String value = queue.poll();
		if (value.equals("#")) {
			return null;
		}
		Node head = new Node(Integer.valueOf(value));
		head.left = reconPreOrder(queue);
		head.right = reconPreOrder(queue);
		return head;
	}

     折纸问题

       请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。 此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。 如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。 给定一个输入参数N,代表纸条都从下边向上方连续对折N次。 请从上到下打印所有折痕的方向。 例如:N=1时,打印: down N=2时,打印: down down up

       拿一张纸按照上述的要求折出来,然后做上标记,最后统计出来以后,第一次折出来的是凹折痕,然后下一次就是在上一次的凹折痕上面出现一个凹折痕,下面出现一个凸折痕,后面都是同样的规律。最后折出来的情况就是,每一棵子树的左子树都是凹折痕,右子树都是凸折痕的一棵树。

public class Code_PaperFolding {

	public static void printAllFolds(int N) {
		printProcess(1, N, true);
	}
    //递归过程,来到了某一个节点
    //i是节点的层数,N一共的层数,down==true 凹   down == false 凸
	public static void printProcess(int i, int N, boolean down) {
		if (i > N) {
			return;
		}
		printProcess(i + 1, N, true);
		System.out.println(down ? "down " : "up ");
		printProcess(i + 1, N, false);
	}

	public static void main(String[] args) {
		int N = 1;
		printAllFolds(N);
	}
}

  • 11
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
二叉树的前序遍历和中序遍历可以确定一棵二叉树,因此可以通过已知的前序遍历和中序遍历来构建出一棵二叉树。而求解二叉树的后序遍历则需要使用递归来实现。 具体的算法流程如下: 1. 如果前序遍历序列和中序遍历序列为空,则返回空节点; 2. 取前序遍历序列的第一个元素作为根节点; 3. 在中序遍历序列中找到根节点,确定左子树和右子树的中序遍历序列; 4. 根据左子树的中序遍历序列和前序遍历序列递归构建左子树; 5. 根据右子树的中序遍历序列和前序遍历序列递归构建右子树; 6. 将根节点加入后序遍历序列中; 7. 返回根节点。 下面是代码实现: ``` #include <iostream> #include <vector> using namespace std; // 二叉树节点结构体 struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; // 根据前序遍历序列和中序遍历序列构建二叉树 TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { if (preorder.empty() || inorder.empty()) { return nullptr; } // 取前序遍历序列的第一个元素作为根节点 int root_val = preorder[0]; TreeNode* root = new TreeNode(root_val); // 在中序遍历序列中找到根节点,确定左子树和右子树的中序遍历序列 int root_idx = 0; for (int i = 0; i < inorder.size(); i++) { if (inorder[i] == root_val) { root_idx = i; break; } } vector<int> left_inorder(inorder.begin(), inorder.begin() + root_idx); vector<int> right_inorder(inorder.begin() + root_idx + 1, inorder.end()); // 根据左子树的中序遍历序列和前序遍历序列递归构建左子树 vector<int> left_preorder(preorder.begin() + 1, preorder.begin() + 1 + left_inorder.size()); root->left = buildTree(left_preorder, left_inorder); // 根据右子树的中序遍历序列和前序遍历序列递归构建右子树 vector<int> right_preorder(preorder.begin() + 1 + left_inorder.size(), preorder.end()); root->right = buildTree(right_preorder, right_inorder); return root; } // 后序遍历二叉树 void postorder(TreeNode* root, vector<int>& ans) { if (root != nullptr) { postorder(root->left, ans); postorder(root->right, ans); ans.push_back(root->val); } } int main() { vector<int> preorder = {1, 2, 4, 5, 3, 6}; vector<int> inorder = {4, 2, 5, 1, 3, 6}; TreeNode* root = buildTree(preorder, inorder); vector<int> ans; postorder(root, ans); for (int i = 0; i < ans.size(); i++) { cout << ans[i] << " "; } cout << endl; return 0; } ``` 输出结果为: ``` 4 5 2 6 3 1 ``` 这就是二叉树的后序遍历序列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值