Loading

左神算法-基础05-二叉树

左神算法-基础05-二叉树

二叉树节点结构

class Node<V>{ 
 V value; 
 Node left; 
 Node right; 
}

用递归和非递归两种方式实现二叉树的先序、中序、后序遍历
图片引自添加链接描述
如何直观的打印一颗二叉树

如何完成二叉树的宽度优先遍历(常见题目:求一棵二叉树的宽度)

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

   public Node(int data) {
      this.value = data;
   }
}
/**
 *              1
 *        2           3
 *     4     5     6     7
 * 递归序:    1->2->4->4->4->2->5->5->5->2->1->3->6->6->6->3->7->7->7->3->1
 * @param head
 */
public static void myPreOrderRecur(Node head) {
   if (head == null) {
      return;
   }
   System.out.print(head.value + "\t");
   myPreOrderRecur(head.left);
   myPreOrderRecur(head.right);
}
public static void myInOrderRecur(Node head) {
   if (head == null) {
      return;
   }
   myPreOrderRecur(head.left);
   System.out.print(head.value + "\t");
   myPreOrderRecur(head.right);
}
public static void myPosOrderRecur(Node head) {
   if (head == null) {
      return;
   }
   myPreOrderRecur(head.left);
   myPreOrderRecur(head.right);
   System.out.print(head.value + "\t");
}

非递归方式实现前序遍历

具体过程:

  1. 首先申请一个新的栈,记为stack;

  2. 将头结点head压入stack中;

  3. 每次从stack中弹出栈顶节点,记为cur,然后打印cur值,如果cur右孩子不为空,则将右孩子压入栈中;如果cur的左孩子不为空,将其压入stack中;

  4. 重复步骤3,直到stack为空.

    img

    public static void myPreOrderUnRecur(Node head) {
       if (head == null) {
          return;
       }
       Stack<Node> stack = new Stack<>();
       stack.push(head);
       while(!stack.isEmpty()) {
          Node cur = stack.pop();
          System.out.print(cur.value);
          //先压右孩子,再压左孩子--->出的顺序就是 先左->后右
          if (cur.right != null) {
             stack.push(cur.right);
          }
          if (cur.left != null) {
             stack.push(cur.left);
          }
       }
       System.out.println();
    }
    

非递归方式实现中序遍历

具体过程:

  1. 申请一个新栈,记为stack,申请一个变量cur,初始时令cur为头节点;

  2. 先把cur节点压入栈中,对以cur节点为头的整棵子树来说,依次把整棵树的左子树压入栈中,即不断令cur=cur.left,然后重复步骤2;

  3. 不断重复步骤2,直到发现cur为空,此时从stack中弹出一个节点记为node,打印node的值,并让cur = node.right,然后继续重复步骤2;

  4. 当stack为空并且cur为空时结束。

    img

    public static void myInOrderUnRecur(Node head) {
       if (head == null) {
          return;
       }
       Stack<Node> stack = new Stack<>();
       Node cur = head;
       while(!stack.isEmpty() || cur != null) {
          while (cur != null) {
             stack.push(cur);
             cur = cur.left;
          }
          cur = stack.pop();
          System.out.print(cur.value + "\t");
          cur = cur.right;
       }
       System.out.println();
    }
    

非递归方式实现后序遍历一

具体过程:

使用两个栈实现

  1. 申请两个栈stack1,stack2,然后将头结点压入stack1中;

  2. 从stack1中弹出的节点记为cur,然后先把cur的左孩子压入stack1中,再把cur的右孩子压入stack1中;

  3. 在整个过程中,每一个从stack1中弹出的节点都放在第二个栈stack2中;

  4. 不断重复步骤2和步骤3,直到stack1为空,过程停止;

  5. 从stack2中依次弹出节点并打印,打印的顺序就是后序遍历的顺序;

    img

    public static void myPosOrderUnRecurTwoStack(Node head) {
       if (head == null) {
          return;
       }
       Stack<Node> stack1 = new Stack<>();
       Stack<Node> stack2 = new Stack<>();
       stack1.push(head);
       while(!stack1.isEmpty()) {
          Node cur = stack1.pop();
          stack2.push(cur);
          if (cur.left != null) {
             stack1.push(cur.left);
          }
          if (cur.right != null) {
             stack1.push(cur.right);
          }
       }
       while (!stack2.isEmpty()) {
          System.out.print(stack2.pop().value + "\t");
       }
       System.out.println();
    }
    

非递归方式实现后序遍历二

具体过程:

使用一个栈实现

  1. 申请一个栈stack,将头节点压入stack,同时设置两个变量 h 和 c,在整个流程中,h代表最近一次弹出并打印的节点,c代表当前stack的栈顶节点,初始时令h为头节点,,c为null;
  2. 每次令c等于当前stack的栈顶节点,但是不从stack中弹出节点,此时分一下三种情况:

(1)如果c的左孩子不为空,并且h不等于c的左孩子,也不等于c的右孩子,则吧c的左孩子压入stack中

(2)如果情况1不成立,并且c的右孩子不为空,并且h不等于c的右孩子,则把c的右孩子压入stack中;

(3)如果情况1和2不成立,则从stack中弹出c并打印,然后令h等于c;

  1. 一直重复步骤2,直到stack为空.

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9q2X2RX-1637408224370)(https://files.cnblogs.com/files/songwenjie/后序遍历2.gif)]

    public static void myPosOrderUnRecurOneStack(Node head) {
       if (head == null) {
          return;
       }
       Stack<Node> stack = new Stack<>();
       stack.push(head);
       Node lastPop = head;   //记录上次访问的节点
       Node curTop = null;       //记录当前的栈顶节点
       while(!stack.isEmpty()) {
          curTop = stack.peek();
          //只要左孩子没被访问的话,右孩子一定没被访问
          if (curTop.left != null && lastPop != curTop.left && lastPop != curTop.right) {
             //curTop有左孩子,并且左孩子没被访问过,右孩子也没被访问过
             stack.push(curTop.left);
          }else if (curTop.right != null && lastPop != curTop.right) {
             //curTop有右孩子,并且右孩子没被访问过
             stack.push(curTop.right);
          }else {
             //curTop没有左右孩子,或者左右孩子都被访问过
             lastPop = stack.pop();
             System.out.print(lastPop.value + "\t");
          }
       }
       System.out.println();
    }
    

层序遍历

具体过程:

  1. 首先申请一个新的队列,记为queue;

  2. 将头结点head压入queue中;

  3. 每次从queue中出队,记为node,然后打印node值,如果node左孩子不为空,则将左孩子入队;如果node的右孩子不为空,则将右孩子入队;

  4. 重复步骤3,直到queue为空。

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

统计一棵二叉树的最大宽度

//使用hashmap的方法
public static int getMaxWidthWithMap(Node head) {
   if (head == null) {
      return 0;
   }
   int curWidth = 0;
   int maxWidth = 0;
   int curLevel = 1;
   HashMap<Node, Integer> levelMap = new HashMap<>();
   Queue<Node> queue = new LinkedList<Node>();
   queue.add(head);
   levelMap.put(head, 1);
   while (!queue.isEmpty()) {
      head = queue.poll();
      if (head.left != null) {
         queue.add(head.left);
         levelMap.put( head.left, levelMap.get(head) + 1);
      }
      if (head.right != null) {
         queue.add(head.right);
         levelMap.put( head.right, levelMap.get(head) + 1);
      }
      if (levelMap.get(head) > curLevel) {   //结算上一层的宽度
         maxWidth = Math.max(maxWidth, curWidth);
         curWidth = 1;
         curLevel = levelMap.get(head);
      }else {
         curWidth++;
      }
   }
   //最后要再结算一次,(最后一层)
   maxWidth = Math.max(maxWidth, curWidth);
   return maxWidth;
}

//使用指针的方法
public static int getMaxWidthWithoutMap(Node head) {
	if (head == null) {
		return 0;
	}
	int maxWidth = 0;
	int curWidth = 0;
	Node curEnd = head;		//当前层最后一个节点
	Node curNextEnd = null;		//下一层最后一个节点
	Queue<Node> queue = new LinkedList<Node>();
	queue.add(head);
	while (!queue.isEmpty()) {
		head = queue.poll();
		if (head.left != null) {
			queue.add(head.left);
			curNextEnd = head.left;
		}
		if (head.right != null) {
			queue.add(head.right);
			curNextEnd = head.right;
		}
		if (head == curEnd) {
			curWidth++;
			maxWidth = Math.max(curWidth, maxWidth);
			curWidth = 0;
			curEnd = curNextEnd;
		}else {
			curWidth++;
		}
	}
	return maxWidth;
}

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

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

如何判断一颗二叉树是完全二叉树?

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

如何判断一颗二叉树是否是平衡二叉树?(二叉树题目套路)

//递归方式中序遍历实现
public static int preValue = Integer.MIN_VALUE;
public static boolean myCheckBSTRecur(Node head) {
   if (head == null) {
      return true;
   }
   if (!myCheckBSTRecur(head.left)) {
      return false;
   }
   if (preValue >= head.value) {
      return false;
   }else {
      preValue = head.value;
   }
   return myCheckBSTRecur(head.right);
}

//非递归中序遍历实现
public static boolean checkBSTUnRecur(Node head) {
   if (head == null) {
      return true;
   }
   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 class ReturnData{
      public boolean isBST;
      public int max;
      public int min;

      public ReturnData(boolean isBST, int max, int min) {
         this.isBST = isBST;
         this.max = max;
         this.min = min;
      }
   }
   public static ReturnData process(Node head) {
      if (head == null) {
         return null;
      }
      ReturnData leftData = process(head.left);
      ReturnData rightData = process(head.right);
      //先令min 和max 都等于自己
      //==========这部分可以优化去掉============
      int min = head.value;
      int max = head.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;  //初始默认它是搜索二叉树,出现不满足的情况就置为false
      if (leftData != null && (!leftData.isBST || leftData.max > head.value) ) {
         isBST = false;
      }
      if (rightData != null && (!rightData.isBST || rightData.min < head.value) ) {
         isBST = false;
      }
      //另一种判断方式,初始为false
      //只有当left.max < value < right.min 时,才置为 true
//    boolean isBST = false;
//    if (
//          (leftData!=null ? (leftData.isBST && leftData.max < x.value) : true)
//                &&
//                (rightData!=null ? (rightData.isBST && rightData.min > x.value) : true)
//    ) {
//       isBST = true;
//    }
      //如果当前树是搜索二叉树的话,当前树的最大值就是右最大值,最小值就是左最小值
      //如果不是的话,isBST就是false,最大最小值也就无所谓了
      return new ReturnData(isBST,rightData.max,leftData.min);
   }
   	//判断是否是完全二叉树
	public static boolean myIsCBT(Node head) {
		if (head == null) {
			return true;
		}
		Queue<Node> queue = new LinkedList<>();
		queue.add(head);
		boolean beginLeaf = false;
		while(!queue.isEmpty()) {
			head = queue.poll();
			if (head.right != null && head.left == null) {    //有右孩子,无左孩子--->一定不是
				return false;
			}
			if (beginLeaf && head.left != null) {    //叶子节点已经开始,它的左孩子必须是空
				return false;
			}
			if (head.left != null) {
				queue.add(head.left);
			}
			if (head.right != null) {
				queue.add(head.right);
			}else {	//右孩子为空,说明叶子节点开始了
				beginLeaf = true;
			}
		}
		return true;
	}
   //是否是满二叉树
   public static boolean isFT(Node head) {
      if (head == null) {
         return true;
      }
      ReturnType data = process(head);
      if (data.nodeNums == (1 << data.height ) - 1 )
         return true;
      return false;
      //==========process2==========
//    return  process2(head) != null;
   }
   public static class ReturnType {
      public int nodeNums;
      public int height;

      public ReturnType(int nodeNums, int height) {
         this.nodeNums = nodeNums;
         this.height = height;
      }
   }
   public static ReturnType process(Node head) {
      if (head == null) {
         return new ReturnType(0,0);
      }
      ReturnType leftData = process(head.left);
      ReturnType rightData = process(head.right);
      return new ReturnType(leftData.nodeNums + rightData.nodeNums + 1,
            Math.max(leftData.height, rightData.height) + 1);
   }	
   public static ReturnType process2(Node head) {
      if (head == null) {
         return new ReturnType(0,0);
      }
      ReturnType leftData = process(head.left);
      ReturnType rightData = process(head.right);
      if (leftData == null || rightData == null) {
         return null;
      }
      if (leftData.height != rightData.height || leftData.nodeNums != rightData.nodeNums){
         return null;
      }
      return new ReturnType(leftData.nodeNums + rightData.nodeNums + 1,
            Math.max(leftData.height, rightData.height) + 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 myProcess(Node head) {
   if (head == null) {
      return new ReturnType(true, 0);
   }
   ReturnType leftData = process(head.left);
   ReturnType rightData = process(head.right);

   boolean isBalanced = true;
   int height = 0;
   if (!leftData.isBalanced || !rightData.isBalanced || Math.abs(leftData.height - rightData.height) > 1) {
      isBalanced = false;
   }
   height = Math.max(leftData.height, rightData.height);

   return new ReturnType(isBalanced,height);
}
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);
   boolean isBalanced = leftData.isBalanced && rightData.isBalanced
         && Math.abs(leftData.height - rightData.height) < 2;
   
    return new ReturnType(isBalanced, height);
}

给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

//递归方式找两个节点的最低公共祖先,
//利用左右子树返回的信息判断o1,o2是分散在两棵树上,还是在其中一棵树上
public static Node myLowestAncestor(Node head, Node o1, Node o2) {
   if (head == null || head == o1 || head == o2) {
      return head;
   }
   Node left = myLowestAncestor(head.left, o1, o2);
   Node right = myLowestAncestor(head.right, o1, o2);
   if (left != null && right != null) {
      return head;
   }
   return left == null ? right : left;
}
//使用map记录所有节点的父节点,从o1,o2依次往上遍历,找到最低公共祖先
public static Node getLowestAncestor(Node head, Node o1, Node o2) {
   if (head == null || head == o1 || head == o2) {
      return head;
   }
   HashMap<Node, Node> map = new HashMap<Node, Node>();
   map.put(head, null);
   LinkedList<Node> nodes = new LinkedList<>();
   nodes.add(head);
   while (!nodes.isEmpty()) {
      Node cur = nodes.poll();
      if (cur.left != null) {
         map.put(cur.left, cur);
         nodes.add(cur.left);
      }
      if (cur.right != null) {
         map.put(cur.right, cur);
         nodes.add(cur.right);
      }
   }
   HashSet<Node> set = new HashSet<>();
   while(map.containsKey(o1)) {
      set.add(o1);
      o1 = map.get(o1);
   }
   while(!set.contains(o2)) {
      o2 = map.get(o2);
   }
   return o2;
}
// 创建一个内部类来解决最低公共祖先的问题,可以使用递归方式赋值map
public static class Record1 {
   private HashMap<Node, Node> map;
   public Record1(Node head) {
      map = new HashMap<Node, Node>();   //记录   孩子--->父亲节点
      if (head != null) {
         map.put(head, null);
      }
      setMap(head);
   }
   private void setMap(Node head) {
      if (head == null) {
         return;
      }
      if (head.left != null) {
         map.put(head.left, head);
      }
      if (head.right != null) {
         map.put(head.right, head);
      }
      setMap(head.left);
      setMap(head.right);
   }
   public Node query(Node o1, Node o2) {
      HashSet<Node> path = new HashSet<Node>();
      while (map.containsKey(o1)) {  //迭代找o1的父节点们,放到set中
         path.add(o1);
         o1 = map.get(o1);
      }
      while (!path.contains(o2)) {
         o2 = map.get(o2);
      }
      return o2;
   }

}

在二叉树中扩找到一个节点的后继节点

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

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的后继节点。

    public static Node myGetSuccessorNode(Node node) {
      if (node == null) {
         return node;
      }
      //情况1:有右树,它的后继就是右树的最左节点
      if (node.right != null) {
         return getLeftMost(node.right);
      } else {
      //情况2:没有右树,他的后继就是自己作为左子树最右节点的头节点
         while (node.parent != null && node != node.parent.left){
            node = node.parent;
         }
         return node.parent;
//       Node parent = node.parent;
//       while (parent != null && node != parent.left) {
//          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;
}
//中序序列化
public static String serialByIn(Node head) {
   if (head == null) {
      return "#!";
   }
   String res = "";
   res += serialByIn(head.left);
   res += head.value + "!";
   res += serialByIn(head.right);
   return res;
}
//无法恢复,递归过程丢失根节点信息
public static Node reconInOrder(Queue<String> queue) {
   return null;
}
//后序序列化
public static String serialByPos(Node head) {
   if (head == null) {
      return "#!";
   }
   String res = "";
   res += serialByPos(head.left);
   res += serialByPos(head.right);
   res += head.value + "!";
   return res;
}
//后序反序列化
public static Node reconByPosString(String preStr) {
	String[] values = preStr.split("!");
	Queue<String> queue = new LinkedList<String>();
    //逆序存到队列中,队首元素就是根节点的值
	for (int i = values.length - 1; i != -1; i--) {
		queue.offer(values[i]);
	}
	return reconPosOrder(queue);
}
public static Node reconPosOrder(Queue<String> queue) {
	String value = queue.poll();
	if (value.equals("#")) {
		return null;
	}
	Node head = new Node(Integer.valueOf(value));
	head.right = reconPosOrder(queue);
	head.left = reconPosOrder(queue);
	return head;
}
//层序序列化
public static String mySerialByLevel(Node head) {
   if (head == null) {
      return "#!";
   }
   String res = head.value + "!";
   Queue<Node> queue = new LinkedList<>();
   queue.add(head);
   while(!queue.isEmpty()) {
      head = queue.poll();
      if (head.left != null) {
         res += head.left.value + "!";
         queue.add(head.left);
      } else {
         res += "#!";   
      }
      if (head.right != null) {
         res += head.right.value + "!";
         queue.add(head.right);
      } else {
         res += "#!";
      }
   }
   return res;
}
//层序反序列化
public static Node myReconByLevelString(String levelStr) {
   String[] values = levelStr.split("!");
   int index = 0;
   if (values[index].equals("#")){
      return null;
   }
   Queue<Node> queue = new LinkedList<Node>();
   Node head = new Node(Integer.valueOf(values[index]));
   queue.add(head);
   while(!queue.isEmpty()) {
      Node cur = queue.poll();
      cur.left = values[++index].equals("#") ? null : new Node(Integer.valueOf(values[index]));
      cur.right = values[++index].equals("#") ? null : new Node(Integer.valueOf(values[index]));
      if (cur.left != null) {
         queue.add(cur.left);
      }
      if (cur.right != null) {
         queue.add(cur.right);
      }
   }
   return head;
}

折纸问题

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。

此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。

如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。

给定一个输入参数N,代表纸条都从下边向上方连续对折N次。

请从上到下打印所有折痕的方向。

例如:N=1时,打印: down N=2时,打印: down down up

public static void printAllFolds(int N) {
   printProcess(1, N, true);
}
public static void myPrintAllFolds(int N) {
   myPrintProcess(1, N, true);
}
//i记录当前深度
//N记录对折次数,也就是二叉树的最大深度
//down用来记录当前节点的值,在递归过程就可以实现输出,无需先构建二叉树,再进行中序遍历
public static void myPrintProcess(int i, int N, boolean down) {
   if (i > N){
      return;
   }
   myPrintProcess(i + 1, N, true);
   System.out.println(down ? "凹" : "凸");
   myPrintProcess(i + 1, N, 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);
}
posted @ 2021-11-20 19:44  zhangj9  阅读(102)  评论(0)    收藏  举报