树是什么?
树是由上往下具有层次结构的数据结构
树的特点
- 每个节点都只有有限个子节点或无子节点
- 没有父节点的节点称为根节点
- 每一个非根节点有且只有一个父节点
- 除了根节点外,每个子节点可以分为多个不相交的子树
- 树里面没有环路
专有名词
- 节点的度:一个节点含有的子树的个数称为该节点的度
- 树的度:一棵树中,最大的节点度称为树的度
- 叶节点或终端节点:度为零的节点
- 非终端节点或分支节点:度不为零的节点
- 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
- 子节点:一个节点含有的子树的根节点称为该节点的子节点
- 兄弟节点:具有相同父节点的节点互称为兄弟节点
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
- 深度:对于任意节点n,n的深度为从根到n的路径长度,根的深度为0
- 高度:对于任意节点n,n的高度为从n到叶节点的路径长度,叶节点的高度为0
- 堂兄弟节点:父节点在同一层的节点互为堂兄弟
- 节点的祖先:从根到该节点所经分支上的所有节点
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙
- 森林:由m(m>=0)棵互不相交的树的集合称为森林
树和图的区别
- 树是图,图不一定是树,树是图的子集
- 树有一个根节点,图没有
- 树可以递归遍历,图要看情况
- 树的非根节点必定有一个父节点,图不一定
- 树是一种层次结构,图是网状结构
树的分类
- 无序树:树中任意节点的子节点之间没有顺序关系
- 有序树:树中任意节点的子节点之间有顺序关系
有序树又分为:
- 二叉树:每个节点最多含有两个子树的树称为二叉树
- 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树
- 满二叉树:所有叶节点都在最底层的完全二叉树
- 二叉搜索树:除叶节点外,其余节点中左节点小于父节点,右节点大于父节点
- 平衡二叉树:当且仅当任何节点的两棵子树的高度差不大于1的二叉搜索树
- B树:平衡多叉树,是平衡二叉树的一般化
- 红黑树:用红、黑色节点简化B树的平衡二叉树
- 左倾红黑树:红节点向左倾斜的红黑树
- 霍夫曼树:带权路径最短的二叉树称为哈夫曼树
二叉树实现(基于链表)
二叉树可用链表和数组实现,但当树完全斜向一边时,基于数组的实现会造成大量的空间浪费,故普通的二叉树通常基于链表
节点数据
class TreeNode<T> {
T value;
TreeNode<T> leftChild;
TreeNode<T> rightChild;
TreeNode(T value) {
this.value = value;
}
}
对二叉树的操作
对二叉树的操作和树本身是无关的,即这些操作对于任何链表实现二叉树都是有效的,故它们都是静态方法,可封装为工具类
获取总节点数
递归获取左右节点的数量,直到叶节点,返回时进行计数
public static <T> int getTreeNum(TreeNode<T> root) {
if (root == null) {
return 0;
}
return getTreeNum(root.leftChild) + getTreeNum(root.rightChild) + 1;
}
获取指定层次节点数
若根节点为空或层次为不正确返回0,只有根节点返回1,其余返回该层次左右节点数量
public static <T> int getCurrentLevelNum(TreeNode<T> root, int level) {
if (root == null || level < 1) {
return 0;
}
if (level == 1) {
return 1;
}
int leftNum = getCurrentLevelNum(root.leftChild, level - 1);
int rightNum = getCurrentLevelNum(root.rightChild, level - 1);
return leftNum + rightNum;
}
获取叶节点数
递归获取左右叶节点的数量,当某节点的左右节点都为空时则该节点是叶节点
public static <T> int getLeafNum(TreeNode<T> root) {
if (root == null) {
return 0;
}
if (root.leftChild == null && root.rightChild == null) {
return 1;
}
int leftNum = getLeafNum(root.leftChild);
int rightNum = getLeafNum(root.rightChild);
return leftNum + rightNum;
}
获取高度/深度
高度为指定节点到叶节点的距离。获取左右节点高度,取最大值,递归到叶节点返回,返回时进行计数
public static <T> int getTreeHeight(TreeNode<T> root) {
if (root == null) {
return 0;
}
int leftDepth = getTreeHeight(root.leftChild) + 1;
int rightDepth = getTreeHeight(root.rightChild) + 1;
return Math.max(leftDepth, rightDepth);
}
深度为根节点到指定节点的距离,指定节点深度=根节点高度-指定节点高度
遍历
- 前序遍历:根节点→左子树→右子树
- 中序遍历:左子树→根节点→右子树
- 后序遍历:左子树→右子树→根节点
- 层次遍历:从上往下从左往右层层遍历
private static <T> void visitNode(TreeNode<T> node) {
System.out.print(node.value + " ");
}
public static <T> void preOrderTraversal(TreeNode<T> root) {
if (root == null) {
return;
}
visitNode(root);
preOrderTraversal(root.leftChild);
preOrderTraversal(root.rightChild);
}
public static <T> void midOrderTraversal(TreeNode<T> root) {
if (root == null) {
return;
}
midOrderTraversal(root.leftChild);
visitNode(root);
midOrderTraversal(root.rightChild);
}
public static <T> void sufOrderTraversal(TreeNode<T> root) {
if (root == null) {
return;
}
sufOrderTraversal(root.leftChild);
sufOrderTraversal(root.rightChild);
visitNode(root);
}
public static <T> void LevelTraversal(TreeNode<T> root) {
Queue<TreeNode<T>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode<T> node = queue.poll();
visitNode(node);
if (node.leftChild != null) {
queue.offer(node.leftChild);
}
if (node.rightChild != null) {
queue.offer(node.rightChild);
}
}
}
搜索
- 广度优先搜索:利用队列先进先出的性质,依次对比根节点及其左右节点,从上往下从左往右层层搜索
- 深度优先搜索:利用栈先进后出的性质,若右节点先入栈,则先遍历左子树,反之则先遍历右子树
public static <T> boolean BreadthFirstSearch(TreeNode<T> root, T value) {
Queue<TreeNode<T>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode<T> node = queue.poll();
visitNode(node);
if (node.value == value) {
return true;
}
if (node.leftChild != null) {
queue.offer(node.leftChild);
}
if (node.rightChild != null) {
queue.offer(node.rightChild);
}
}
return false;
}
public static <T> boolean DepthFirstSearch(TreeNode<T> root, T value) {
Stack<TreeNode<T>> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode<T> node = stack.pop();
visitNode(node);
if (node.value == value) {
return true;
}
if (node.leftChild != null) {
stack.push(node.leftChild);
}
if (node.rightChild != null) {
stack.push(node.rightChild);
}
}
return false;
}
重构树
根据前序和中序重构树:前序第一个为根节点,然后根据中序分为左右子树,获取左子树前序和中序,递归构建左子树,右子树同理
public static <T> TreeNode<T> getTreeByPreOrderAndMidOrder(List<T> pre, List<T> mid) {
if (pre == null || mid == null || pre.size() == 0 || mid.size() == 0) {
return null;
}
if (pre.size() == 1) {
return new TreeNode<T>(pre.get(0));
}
TreeNode<T> root = new TreeNode<T>(pre.get(0));
int index = mid.indexOf(pre.get(0));
List<T> leftPre = pre.subList(1, index + 1);
List<T> leftMid = mid.subList(0, index);
root.leftChild = getTreeByPreOrderAndMidOrder(leftPre, leftMid);
List<T> rightPre = pre.subList(index + 1, pre.size());
List<T> rightMid = mid.subList(index + 1, mid.size());
root.rightChild = getTreeByPreOrderAndMidOrder(rightPre, rightMid);
return root;
}
根据后序和中序重构树:后序最后一个为根节点,然后根据中序分为左右子树,获取左子树的后序和中序,递归构建左子树,右子树同理
public static <T> TreeNode<T> getTreeBySufOrderAndMidOrder(List<T> suf, List<T> mid) {
if (suf == null || mid == null || suf.size() == 0 || mid.size() == 0) {
return null;
}
if (suf.size() == 1) {
return new TreeNode<T>(suf.get(0));
}
TreeNode<T> root = new TreeNode<T>(suf.get(suf.size() - 1));
int index = mid.indexOf(suf.get(suf.size() - 1));
List<T> leftSuf = suf.subList(0, index);
List<T> leftMid = mid.subList(0, index);
root.leftChild = getTreeBySufOrderAndMidOrder(leftSuf, leftMid);
List<T> rightSuf = suf.subList(index, suf.size() - 1);
List<T> rightMid = mid.subList(index + 1, mid.size());
root.rightChild = getTreeBySufOrderAndMidOrder(rightSuf, rightMid);
return root;
}
Tips:
- 在定位根节点时,若出现与根节点一样的元素,indexOf只会返回第一个对应元素的索引,会导致错误
测试代码
构造树
TreeNode<Integer> node1 = new TreeNode<>(1);
TreeNode<Integer> node2 = new TreeNode<>(2);
TreeNode<Integer> node3 = new TreeNode<>(3);
TreeNode<Integer> node4 = new TreeNode<>(4);
TreeNode<Integer> node5 = new TreeNode<>(5);
TreeNode<Integer> node6 = new TreeNode<>(6);
node1.leftChild = node2;
node1.rightChild = node3;
node2.leftChild = node4;
node2.rightChild = node5;
node3.leftChild = node6;
测试树
System.out.print("前序遍历:");
preOrderTraversal(node1);
System.out.println();
System.out.print("中序遍历:");
midOrderTraversal(node1);
System.out.println();
System.out.print("后序遍历:");
sufOrderTraversal(node1);
System.out.println();
System.out.print("层次遍历: ");
LevelTraversal(node1);
System.out.println();
System.out.print("广度优先搜索:");
boolean BreadthFirstResult = BreadthFirstSearch(node1, 5);
System.out.print(BreadthFirstResult ? "found" : "not found");
System.out.println();
System.out.print("深度优先搜索:");
boolean DepthFirstResult = DepthFirstSearch(node1, 5);
System.out.print(DepthFirstResult ? "found" : "not found");
System.out.println();
System.out.println("树高度:" + getTreeHeight(node1));
System.out.println("节点2深度:" + (getTreeHeight(node1) - getTreeHeight(node2)));
System.out.println("总节点数:" + getTreeNum(node1));
System.out.println("叶节点数:" + getLeafNum(node1));
System.out.println("第三层节点数:" + getCurrentLevelNum(node1, 3));
TreeNode<String> rebuildTree1 = getTreeByPreOrderAndMidOrder(Arrays.asList("1 2 4 5 3 6".split(" ")), Arrays.asList("4 2 5 1 6 3".split(" ")));
System.out.print("\n根据前序和中序重建树,其后序遍历:");
sufOrderTraversal(rebuildTree1);
System.out.println();
TreeNode<String> rebuildTree2 = getTreeBySufOrderAndMidOrder(Arrays.asList("4 5 2 6 3 1".split(" ")), Arrays.asList("4 2 5 1 6 3".split(" ")));
System.out.print("\n根据后序和中序重建树,其前序遍历:");
preOrderTraversal(rebuildTree2);
System.out.println();
如上打印
前序遍历:1 2 4 5 3 6
中序遍历:4 2 5 1 6 3
后序遍历:4 5 2 6 3 1
层次遍历: 1 2 3 4 5 6
广度优先搜索:1 2 3 4 5 found
深度优先搜索:1 3 6 2 5 found
树高度:3
节点2深度:1
总节点数:6
叶节点数:3
第三层节点数:3
根据前序和中序重建树,其后序遍历:4 5 2 6 3 1
根据后序和中序重建树,其前序遍历:1 2 4 5 3 6