「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
二叉树的概念有很多,比如树高度,节点层数,节点度数,路径,叶子节点,分支节点,根节点,父节点,左节点,右节点,兄弟节点,祖先节点,子孙节点,左子树,右子树等基本概念,有些概念是树共有的,有些是只有二叉树才有的,容易理解,就不一一细说。主要说说二叉树的分类、二叉树的四种遍历方式以及实现。
一、二叉树分类
1.满二叉树
二叉树的每一层的节点数都达到了最大值((每层节点的数量最大值为2的n-1次方))。
2.完全二叉树
假设二叉树的高度为n,除第 n 层外,从第1层到第n-1层的节点数都达到最大个数(前n-1层可以看成一个满二叉树),第n层有叶子节点,并且叶子节点都是从左到右依次排列,这就是完全二叉树。完全二叉树的节点必须满足:某个节点没有左子树那它一定没有右子树,如图
3.二叉查找树
二叉查找树(Binary Search Tree),也称为二叉搜索树,二叉排序树,它可能是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉查找树的特点:
-
在二叉查找树中,左子节点的值始终比根结点小,右子节点的值始终比根结点大
-
任意结点的左右子树也都是二叉查找树
-
通过中序遍历,将得到的是一个有序的数列
如图所示,第一种情况是最优情况,第二种情况是最坏情况,效率等同于链表。
4.平衡二叉树
平衡二叉树(Balanced Binary Tree),又被称为AVL树(跟AVL算法有区别,不要混淆),它是二叉查找树最优的情况。它很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。平衡二叉树的特点:
-
必须满足二叉查找树的条件
-
它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
-
当删除、新增、修改节点上的值时,它会通过左旋或右旋的操作使二叉树保持平衡
-
最坏的时间复杂度为O(log2n)
二、二叉树的遍历以及实现方式
根据根节点的遍历顺序分为前序、中序、后序,另外还有按层次从上到下从左到右的层序遍历。以下是代码实现:
public class BNode { //节点类
public Object element; //节点元素的值
public BNode lChild; //左子节点
public BNode rChild; //又子节点
public BNode(Object element, BNode lChild, BNode rChild) {
this.element = element;
this.lChild = lChild;
this.rChild = rChild;
}
public BNode(Object element){
this.element = element;
}
}
- 前序遍历:根节点->左子树->右子树
- 中序遍历:左子树->根节点->右子树
- 后序遍历:左子树->右子树->根节点
- 层序遍历:从根节点开始,从上到下,从左到右。
public class BinaryTree {
public static BNode root; //根节点
public static ArrayList<BNode> list = new ArrayList<BNode>(); //用来存放便利的元素
public static LinkedList<BNode> queue = new LinkedList<>(); //用来进行层序遍历的队列
/**
* 对该二叉树进行前序遍历 结果存储到list中 前序遍历
*/
public static void preOrder(BNode node) {
list.add(node); // 先将根节点存入list
// 如果左子树不为空继续往左找,在递归调用方法的时候一直会将子树的根存入list,这就做到了先遍历根节点
if (node.lChild != null) {
preOrder(node.lChild);
}
// 无论走到哪一层,只要当前节点左子树为空,那么就可以在右子树上遍历,保证了根左右的遍历顺序
if (node.rChild != null) {
preOrder(node.rChild);
}
}
//中序遍历
public static void inOrder(BNode node) {
if (node.lChild != null) {
inOrder(node.lChild);
}
list.add(node);
if (node.rChild != null) {
inOrder(node.rChild);
}
}
//后序遍历
public static void postOrder(BNode node) {
if (node.lChild != null) {
postOrder(node.lChild);
}
if (node.rChild != null) {
postOrder(node.rChild);
}
list.add(node);
}
//层序遍历
public static void levelOrder(BNode node){
if (node == null){
return;
}
BNode currNode = null; //定义一个当前节点,指向出队的节点
queue.offer(node); //根节点入队
while (!queue.isEmpty()){ //队列非空则循环
currNode = queue.poll(); //当前节点,也就是刚出队的节点
System.out.print(currNode.element.toString());
if (currNode.lChild!=null){ //判断是否有左子节点
queue.offer(currNode.lChild); //左子节点存在则入队
}
if (currNode.rChild!=null){ //判断是否有右子节点
queue.offer(currNode.rChild); //右子节点存在则入队
}
}
}
public static void init(){ //初始化一颗二叉树,并赋值给root
BNode A = new BNode("A");
BNode B = new BNode("B");
BNode C = new BNode("C");
BNode D = new BNode("D");
BNode E = new BNode("E");
BNode F = new BNode("F");
BNode G = new BNode("G");
BNode H = new BNode("H");
A.lChild = B;
B.lChild = D;
B.rChild = E;
E.rChild = F;
A.rChild = C;
C.rChild = G;
C.lChild = H;
root = A;
}
public static void main(String[] args) {
init();
//preOrder(root); //ABDEFCHG
//inOrder(root); //DBEFAHCG
//postOrder(root); //DFEBHGCA
levelOrder(root); //ABCDEHGF
for (BNode node:list) {
System.out.print(node.element.toString());
}
}
}
前序、中序、后序遍历的方式都是采用递归的方式进行的,很好理解。层序遍历借用了一个队列进行,依次入队父节点(根节点)、左子节点、右子节点,队列为空则遍历完成。