二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2^{i-1}个结点;深度为k的二叉树至多有2^k-1个结点;对任何一棵二叉树T,如果其终端结点数为n_0,度为2的结点数为n_2,则n_0=n_2+1。
一棵深度为k,且有2^k-1个节点称之为满二叉树;深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中,序号为1至n的节点对应时,称之为完全二叉树。
1 满二叉树和完全二叉树
满二叉树一定是完全二叉树,但是反过来就不一定。满二叉树的定义是除了叶子结点,其它结点左右孩子都有,深度为k的满二叉树,结点数就是2的k次方减1。完全二叉树是每个结点都与深度为k的满二叉树中编号从1到n一一对应。
2 树的深度
树的最大层次就是深度,比如上图,深度是3。很容易得出,深度为k的树,拥有的最大结点数是2的k次方减1。
3 树的孩子,兄弟,双亲
上图中,B,C是A的孩子,B,C之间互为兄弟,A是B,C的双亲。
笔试/面试常见的2个问题:
1、写出三种遍历方式的代码;
2、给出先序、中序的结果, 给出后续的结果;(考验你是否真的理解三种排序)
public class TestClass {
private static int[] array = { 1, 2, 3, 4, 5, 6, 7};
private static List<Node> nodeList = null;
private static class Node {
Node leftChild; //左子树
Node rightChild; //右子树
int data; //节点数据
Node(int newData) {
leftChild = null;
rightChild = null;
data = newData;
}
}
public static void createBinTree() {
nodeList = new LinkedList<Node>();
for (int nodeIndex = 0; nodeIndex < array.length; nodeIndex++) {
nodeList.add(new Node(array[nodeIndex]));
}
for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {
nodeList.get(parentIndex).leftChild = nodeList
.get(parentIndex * 2 + 1);
nodeList.get(parentIndex).rightChild = nodeList
.get(parentIndex * 2 + 2);
}
int lastParentIndex = array.length / 2 - 1;
nodeList.get(lastParentIndex).leftChild = nodeList
.get(lastParentIndex * 2 + 1);
if (array.length % 2 == 1) {
nodeList.get(lastParentIndex).rightChild = nodeList
.get(lastParentIndex * 2 + 2);
}
}
/**
* 先序遍历
*/
public static void preOrderTraverse(Node node) {
if (node == null)
return;
System.out.print(node.data + " ");
preOrderTraverse(node.leftChild);
preOrderTraverse(node.rightChild);
}
/**
* 中序遍历
*/
public static void inOrderTraverse(Node node) {
if (node == null)
return;
inOrderTraverse(node.leftChild);
System.out.print(node.data + " ");
inOrderTraverse(node.rightChild);
}
/**
* 后序遍历
*/
public static void postOrderTraverse(Node node) {
if (node == null)
return;
postOrderTraverse(node.leftChild);
postOrderTraverse(node.rightChild);
System.out.print(node.data + " ");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
createBinTree();
// nodeList中第0个索引处的值即为根节点
Node root = nodeList.get(0);
System.out.print("先序:");
preOrderTraverse(root);
System.out.println("");
System.out.print("中序:");
inOrderTraverse(root);
System.out.println("");
System.out.print("后序:");
postOrderTraverse(root);
}
}
按照层级遍历, 原理是使用FIFO队列实现。
//层级遍历
private static void levelTraverse(Node node) {
if (node == null) {
return;
}
LinkedList<Node> queue = new LinkedList<>();
Node current = null;
queue.offer(node);
while (!queue.isEmpty()) {
current = queue.poll();
System.out.println(current.data + ""); //访问节点
if (current.leftChild != null) {
queue.offer(current.leftChild)
}
if (current.rightChild != null) {
queue.offer(current.rightChild);
}
}
}
稍微修改一下按层级遍历的代码,还可以实现获取二叉树最大深度、最小深度、每层节点宽度/最大值等等;public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new ArrayList<>();
LinkedList<TreeNode> ll = new LinkedList<>();
ll.add(root);
//队列里是每层节点, 内循环遍历当前层所有节点,然后队列里是下层节点
//本算法还计算二叉树最浅深度, 在外循环深度自增1,内循环判断左右节点为空时跳出循环即可
while (true) {
int len = ll.size(); //每层节点个数
if (len == 0) {
break;
}
int max = 0;
while (len > 0) { //遍历当前层所有节点
TreeNode node = ll.poll(); //从队列头部移出
if (node.left != null) {
ll.add(node.left); //添加到队列尾部
}
if (node.right != null) {
ll.add(node.right);
}
if (node.val>max) max = node.val;
len--;
}
result.add(max);
}
return result;
}
先序:1 2 4 5 3 6 7
中序:4 2 5 1 6 3 7
后序:4 5 2 6 7 3 1
面试时可能告诉你先序和中序的结果, 让你画出二叉树并写出后序排序。
解题技巧:
1、先序排序的第一个数据是根节点, 中序排序中根节点左边的都是左子树、右边的都是右子树。 即1是根节点, 4、2、5是左子树,6、3、7是右子树;
2、 先序的第2个值即2是根节点左子节点;在先序里的3、6、7中第一个值是根节点的右子节点。
3、记住三种排序是按照根节点的读取时序定义, 先序是先访问根节点,中序是第2次访问根节点,后序是第3次访问根节点。