1.树当中的概念
(1)节点的度:一个节点含有的子树的个数称为该节点的度.
(2)树的度:一棵树中,最大的节点的度称为树的度.
(3)叶子节点或终端节点:度为0的节点称为叶节点.
(4)双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点.
(5)孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点.
(6)根结点:一棵树中,没有双亲结点的结点.
(7)节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推.
(8)树的高度或深度:树中节点的最大层次.
2.二叉树
(1)定义:一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成.
(2)特性:
a)每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点.
b)二叉树的子树有左右之分,其子树的次序不能颠倒.
(3)完全二叉树:完全二叉树是效率很高的数据结构。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树。
(4)一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
3.二叉树的性质
例题:一个完全二叉树中有1000个节点,请问:有 ( 500 ) 个叶子节点,( 499 )个非叶子节点,( 1 )个节点只有左孩子,( 0 )个节点只有右孩子。
4.二叉树的遍历方式
(1)先序遍历(前序)==先根遍历:先访问根节点,再递归遍历左子树,再递归遍历右子树.
(2)中序遍历:先递归遍历左子树,再访问根节点,再递归遍历右子树.
(3)后序遍历:先递归遍历左子树,再递归遍历右子树,最后访问根节点.
(4)层序遍历:按照每一层元素从左到右遍历即可.
5.用代码实现二叉树的遍历方式
(1)递归前序遍历图解:
(2)递归中序遍历图解:
(3)递归实现后序遍历图解:
(4)非递归实现前序遍历:
(5)非递归实现中序遍历:
(6)非递归实现后序遍历:
(7)层序遍历:
(8)判断完全二叉树:
import java.util.*;
/**
* Description:二叉树的实现:核心思路要明白每次在递归的时候传入的root是不同的。
*/
class Node {
char value;
Node left;
Node right;
public Node(char value) {
this.value = value;
}
}
public class BinaryTree {
public 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');
Node H = new Node('H');
A.left = B;
B.left = D;
B.right = E;
E.right = H;
A.right = C;
C.left = F;
C.right = G;
return A;
}
// 递归实现前序遍历
void preOrderTraversal(Node root) {
if (root == null) {
return;
}
System.out.print(root.value + " ");
preOrderTraversal(root.left);//需要理解每一次传入的root是哪个
preOrderTraversal(root.right);
}
//非递归实现前序遍历(难点)
void preOrderTraversal2(Node root) {
List<Character> list = new ArrayList<>();//oj
if (root == null) {
return;
}
Stack<Node> stack = new Stack<>();
//1.让cur指向root
Node cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
//2.
stack.push(cur);
System.out.print(cur.value + " ");
list.add(cur.value);
cur = cur.left;
}
cur = stack.pop();
cur = cur.right;
}
// return list;
}
// 中序遍历
void inOrderTraversal(Node root) {
if (root == null) {
return;
}
inOrderTraversal(root.left);
System.out.print(root.value + " ");
inOrderTraversal(root.right);
}
//非递归实现中序遍历(难点)
void inOrderTraversal2(Node root) {
List<Character> list = new ArrayList<>();//oj当中写需要使用
if (root == null) {
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
//先将cur入栈,但不打印,然后cur一直往左走,直到cur.left为空的时候,
//当前的栈顶元素就是cur,然后将cur出栈再打印,然后cur开始走right
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
System.out.print(cur.value + " ");
cur = cur.right;
}
}
// 后序遍历
void postOrderTraversal(Node root) {
if (root == null) {
return;
}
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.print(root.value + " ");
}
// 非递归实现后序遍历(难点)
void postOrderTraversal2(Node root) {
if (root == null) {
return;
}
Stack<Node> stack = new Stack<>();
Node cur = root;
Node flg = null;//flg标记的是打印的元素
while (cur != null || !stack.isEmpty()) {
//节点不为空一直压栈
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
//cur==null,cur需要拿到栈顶元素看是否有右子树
cur = stack.peek();
if (cur.right == null || cur.right == flg) {
System.out.print(cur.value + " ");
stack.pop();
flg = cur;
cur = null;
} else {
cur = cur.right;
}
}
}
// 遍历思路-求结点个数
static int size = 0;
int getSize1(Node root) {
if (root == null) {
return 0;
}
size++;
getSize1(root.left);
getSize1(root.right);
return size;
}
// 子问题思路-求结点个数---面试
int getSize2(Node root) {
if (root == null) {
return 0;
}
return getSize2(root.left) + getSize2(root.right) + 1;
}
// 子问题思路-求叶子结点个数
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 层结点个数
int getKLevelSize(Node root, int k) {
if (root == null || k < 1) {
return 0;
}
if (k == 1) {
// k 为 1 的时候, 就一个根节点
return 1;
}
// 求第 k 层节点的个数,求左子树的第 k - 1 层节点的个数 + 右子树的 k - 1 层
return getKLevelSize(root.left, k - 1) + getKLevelSize(root.right, k - 1);
}
// 查找 val 所在结点,没有找到返回 null,按照根 -> 左子树 -> 右子树的顺序进行查找
// 一旦找到,立即返回,不需要继续在其他位置查找
Node find(Node root, int val) {
if (root == null) {
return null;
}
if (root.value == val) {
return root;
}
/* if (root.left.value == val) {
return find(root.left, val);
}
if (root.right.value == val) {
return find(root.right, val);
}*/
Node result = find(root.left, val);
if (result != null) {
return result;
}
Node result2 = find(root.left, val);
if (result2 != null) {
return result2;
}
return null;
}
// 层序遍历:核心方法是借助队列来实现
void levelOrderTraversal(Node root) {
// 创建一个队列辅助进行遍历
Queue<Node> queue = new LinkedList<>();
//1.当根节点不为空的时候,就把根节点入队。
if (root != null) {
queue.offer(root);
}
while (!queue.isEmpty()) {
//2.当队列不为空的时候,循环取队首元素. 访问这个元素.
Node cur = queue.peek();
// 3. 把当前这个队首元素左子树和右子树都插入队列中.
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
Node output = queue.poll();
if (output == null) {
return;
}
System.out.print(output.value + " ");
}
}
// 判断一棵树是不是完全二叉树(重点)
//核心思路:借助队列(队列先进先出)列来完成,使用层序遍历的方式.
boolean isCompleteTree(Node root) {
Queue<Node> queue = new LinkedList<>();
//1.当根节点不为空的时候,就把根节点入队。
if (root != null) {
queue.offer(root);
}
//2.当队列不为空的时候,让出队的元素为cur.
while (!queue.isEmpty()) {
Node cur = queue.poll();
//3.如果cur不为空,就把cur的左子树和右子树也入队
if (cur != null) {
queue.offer(cur.left);
queue.offer(cur.right);
} else {
break;
}
}
//4.遍历队列中的元素,进行判断;
// 完全二叉树--cur在出队的时候若遇到null,即把二叉树遍历完了,就认为是完全二叉树;
// 如果在cur遇到null之后,而null之后还有其他元素,就不是二叉树.
while (!queue.isEmpty()) {
Node cur = queue.poll();
if (cur != null) {
return false;
}
}
return true;
}
}