二叉树
基本概念
二叉树是n(n≥0)个结点的有限集合:
① 或者为空二叉树,即n = 0。
② 或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
特点:
①每个结点至多只有两棵子树 。
②左右子树不能颠倒(二叉树是有序树)。
二叉树的5种状态:
特殊二叉树
满二叉树: 一棵高度为h,且含有2h - 1个结点的二叉树。
特点:
①只有最后一层有叶子结点。
②不存在度为 1 的结点。
③按层序从 1 开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1;结点 i 的父节点为 i/2 (如果有的话)。
完全二叉树: 当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点相对应时,称为完全二叉树。
特点:
①只有最后两层可能有叶子结点。
②最多只有一个度为1的结点。
③按层序从 1 开始编号,结点 i 的左孩子为 2i,右孩子为 2i+1;结点 i 的父节点为 i/2 (如果有的话)。
④ i ≤ n/2 为分支结点, i > n/2 为叶子结点。
需要注意的是,完全二叉树中,如果某节点只有一个孩子节点,那么这个孩子节点一定是左孩子节点。
二叉排序树:
一棵二叉树或者是空二叉树,或者是具有如下性质的二叉树:
1.左子树上所有结点的关键字均小于根结点的关键字;
2.右子树上所有结点的关键字均大于根结点的关键字。
3.左子树和右子树又各是一棵二叉排序树。
二叉排序树可用于元素的排序、搜索!
平衡二叉树:
树上任一结点的左子树和右子树的深度之差不超过1。
二叉树的性质
1.设非空二叉树中度为0、1和2的结点个数分别为n0、n1和n2,则 n0 = n2 + 1(叶子结点比二分支结点多一个)。
2.二叉树第 i 层至多有 2^(i-1) 个结点(i≥1)。
3.高度为h的二叉树至多有 2^ℎ − 1个结点(满二叉树)。
4.具有n个(n > 0)结点的完全二叉树的高度h为 [log2(n)]+1。其中 log2(n)取整数。
5.对于完全二叉树,可以由的结点数 n 推出度为0、1和2的结点个数为n0、n1和n2 ,完全二叉树最多只有一个度为1的结点,即:若完全二叉树有2k个(偶数)个结点,则必有 n1=1, n0 = k, n2 = k-1;若完全二叉树有2k-1个(奇数)个结点,则必有 n1=0, n0 = k, n2 = k-1。
二叉树的遍历
二叉树的遍历分成:先序遍历,中序遍历,后序遍历,层序遍历!
两种方法实现二叉树遍历
//二叉树的遍历(递归法 和 迭代法)
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
@Override
public String toString() {
return "[" + val + "]";
}
}
public class BinaryTree2 {
//创建一棵树(手动写死的方式)
public static TreeNode build() {
TreeNode a = new TreeNode(1);
TreeNode b = new TreeNode(2);
TreeNode c = new TreeNode(3);
TreeNode d = new TreeNode(4);
TreeNode e = new TreeNode(5);
TreeNode f = new TreeNode(6);
TreeNode g = new TreeNode(7);
a.left = b;
a.right = c;
b.left = d;
b.right = e;
e.left = g;
c.right = f;
return a;
}
//前序遍历(递归法)
public static List<Integer> preorder_Recursion(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result; //这里当root为null需要返回的是result,尽量不要用null!
}
//访问根节点. 此处的 "访问" 不是打印, 而是插入 result 末尾
result.add(root.val);
//递归处理左子树, 此时会得到一个左子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(preorder_Recursion(root.left));
//递归处理右子树, 此时会得到一个右子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(preorder_Recursion(root.right));
return result;
}
//前序遍历(迭代法)
/*
创建一个Stack用来存放节点,首先我们想要打印根节点的数据,
此时Stack里面的内容为空,所以我们优先将头结点加入Stack,然后打印。
之后我们应该先打印左子树,然后右子树。所以先加入Stack的就是右子树,然后左子树。
*/
public static List<Integer> preorder_Iteration(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
//中序遍历(递归法)
public static List<Integer> inorder_Recursion(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result; //这里当root为null需要返回的是result,尽量不要用null!
}
//递归处理左子树, 此时会得到一个左子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(inorder_Recursion(root.left));
//访问根节点. 此处的 "访问" 不是打印, 而是插入 result 末尾
result.add(root.val);
//递归处理右子树, 此时会得到一个右子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(inorder_Recursion(root.right));
return result;
}
//中序遍历(迭代法)
/*
先创建一个Stack,然后按 左 中 右的顺序输出节点。
尽可能的将祖宗节点的左子树压入Stack,此时栈顶的元素是最左侧的元素,
其目的是找到一个最小单位的子树(也就是最左侧的一个节点),并且在寻找的过程中记录了来源,
才能返回上层,同时在返回上层的时候已经处理完毕左子树了。。
当处理完最小单位的子树时,返回到上层处理了中间节点。
处理流程就是: 左子树->中间(祖宗节点)->右子树)
如果有右节点,其也要进行中序遍历。当整个左子树退栈的时候这个时候输出了该二叉树的根节点,
之后输出中间祖宗节点 。然后处理右子树。
*/
public static List<Integer> inorder_Iteration(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
TreeNode cur = root;
Stack<TreeNode> stack = new Stack();
while (!stack.isEmpty() || cur != null) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
cur = node.right;
}
}
return res;
}
//后序遍历(递归法)
public static List<Integer> postorder_Recursion(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result; //这里当root为null需要返回的是result,尽量不要用null!
}
//递归处理左子树, 此时会得到一个左子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(postorder_Recursion(root.left));
//递归处理右子树, 此时会得到一个右子树的遍历结果的 List. 这个结果也要加入到 result 中
result.addAll(postorder_Recursion(root.right));
//访问根节点. 此处的 "访问" 不是打印, 而是插入 result 末尾
result.add(root.val);
return result;
}
//后续遍历(迭代法)
/*
前序遍历的过程 是 中左右。后续遍历就是将其转化成 中右左。
也就是压栈的过程中优先压入左子树,在压入右子树。
然后将这个结果返回来,这里是利用栈的先进后出倒序打印。
*/
public static List<Integer> postorder_Iteration(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
stack2.push(node);
if (node.left != null) {
stack1.push(node.left);
}
if (node.right != null) {
stack1.push(node.right);
}
}
while (!stack2.isEmpty()){
res.add(stack2.pop().val);
}
return res;
}
public static void main(String[] args) {
TreeNode root = build();
System.out.print("前序遍历二叉树(递归法):"+preorder_Recursion(root));
System.out.print("\n"+"前序遍历二叉树(迭代法):"+preorder_Iteration(root));
System.out.print("\n"+"中序遍历二叉树(递归法):"+inorder_Recursion(root));
System.out.print("\n"+"中序遍历二叉树(迭代法):"+inorder_Iteration(root));
System.out.print("\n"+"后序遍历二叉树(递归法):"+postorder_Recursion(root));
System.out.print("\n"+"后序遍历二叉树(迭代法):"+postorder_Iteration(root));
}
}
运行结果:
二叉树中的一些常用操作:
public class Node {
String val;
Node left;
Node right;
public Node(String val) {
this.val = val;
}
}
package Homework0313;
class BinaryTree {
//创建一棵树(手动写死的方式)
public static Node build() {
Node a = new Node("A");
Node b = new Node("B");
Node c = new Node("C");
Node d = new Node("D");
Node e = new Node("E");
Node f = new Node("F");
Node g = new Node("G");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
e.left = g;
c.right = f;
return a;
}
//求节点个数
// 方法1
//使用这个成员变量count,来记录元素个数
public static int count = 0;
public static void length1(Node root){
if (root == null){
return;
}
//访问根节点,此时的访问操作就是count++
count++;
//递归处理左子树
length1(root.left);
//递归处理右子树
length1(root.right);
}
//方法2:(通过方法的返回值记录元素)
public static int length2(Node root){
if (root == null){
return 0;
}
//当前树的节点个数=根结点的个数+左子树的结点数+右子树的节点数
return 1 + length2(root.left) + length2(root.right);
}
//求叶子节点的个数
//方法1
public static int leafsize = 0;
public static void getleafSize1(Node root){
//针对二叉树边遍历,判断当前节点是否为叶子节点,如果是就size++
if (root == null){
return;
}
//判断当前节点是否是叶子节点
if (root.left == null && root.right == null){
leafsize++;
}
getleafSize1(root.left);
getleafSize1(root.right);
}
//方法2:
public static int getleafSize2(Node root){
if (root == null){
return 0;
}
if (root.left == null && root.right == null){
return 1;
}
return getleafSize2(root.left) + getleafSize2(root.right);
}
//求第K层节点的个数
public static int getKLevelSize(Node root,int k){
if (root == null || k<1){
return 0;
}
if (k == 1){
//当前树只有一个根节点时
return 1;
}
return getKLevelSize(root.left,k-1) + getKLevelSize(root.right,k-1);
}
//获取二叉树的高度
public static int getHeight(Node root){
if (root == null){
return 0;
}
int LeftHeight = getHeight(root.left);
int RightHeight = getHeight(root.right);
return 1+(LeftHeight > RightHeight ? LeftHeight : RightHeight);
}
// 查找 val 所在结点,没有找到返回 null,按照 根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
public static Node find(Node root,String toFind){
if (root == null){
return null;
}
if (root.val.equals(toFind)){
return root;
}
Node result = find(root.left,toFind);
if (result != null){
return result;
}
return find(root.right,toFind);
}
public static void main(String[] args) {
Node root = build();
length1(root);
System.out.print("\n"+"方法1求二叉树节点的个数:"+count);
System.out.print("\n"+"方法2求二叉树节点的个数:"+length2(root));
getleafSize1(root);
System.out.print("\n"+"方法1求该二叉树叶子节点个数:"+leafsize);
System.out.print("\n"+"方法2求该二叉树叶子节点个数:"+getleafSize2(root));
System.out.print("\n"+"求二叉树第2层节点的个数:"+getKLevelSize(root,2));
System.out.print("\n"+"求二叉树的高度:"+getHeight(root));
System.out.print("\n"+"查找节点值为“E”的节点是二叉树中的第几节点?:"+find(root,"E"));
}
}
运行结果: