数据结构-二叉树(java)

树的概念

树是一种相对复杂的数据结构(相对于线性表),其数据节点分为三类:根节点、叶子节点、普通节点。普通节点只有一个前驱节点(付节点)和多个后继节点;根节点可以有多个后继节点,没有前驱;叶子节点,只有1个前驱节点,没有后继节点。

树的度:某个节点的后继节点个数称为该节点的度,所有节点中最大的度称为树的度。
树的高度:树的最大层数即为数据的高度。

有一种常用且特殊的数,其度为2,称为二叉树。为其添加不同的限制,又可以划分出很多二叉树的变种。日常开发中很少直接使用简单的二叉树,而是根据不同的场景使用这些变种的二叉树。

各种常见的二叉树

普通二叉树:每个节点最多有两个子树的树结构,分别称为左子树和右子树。如果一棵树的所有节点都只有左子树或者右子树,其实就退化为线性表,这种情况其实是不希望出现的。

满二叉树:由于普通二叉树存在退化为线性表的情况,满二叉树限定所有的分支节点都有左子树和右子树,并且叶子节点都在同一层上。此时就会形成一个等边三角形,节点的总数刚好是2d-1,d为数的高度。

完全二叉树:满二叉树是一种理想状态,节点数刚好满足2d-1的情况是少数。完全二叉树在满二叉树的基础上放宽了限制:最下层的叶子节点都在左边并且连续。换句话说满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树。完全二叉树具有如下特征(节点总树为n,节点i为第i个节点):
1、非叶子节点i,如果2i<=n,则该节点存在左子节点,且左子节点为第2i个节点。否则如果2i>n,则该节点没有左子节点(更没有右子节点)。
2、非叶子节点i,如果2i+1<=n,则该节点存在右节点,且左子节点为第2i+1个节点,否则如2i+1>n,则该节点没有右子节点。

二叉堆:二叉堆首先是一棵完全二叉树,并且所有非叶子结点的值均不大于(或不小于)其左右孩子节点的值。如果均不大于左右孩子节点,为小顶堆;如果均不小于左右孩子节点,则为大顶堆。二叉堆是进行堆排序的基础,同时也是构建优先队列的基础。插入操作对应节点上浮,删除操作对应节点的下沉操作。Java中的PriorityQueue就是使用二叉堆实现的。

二叉排序树:又称二叉搜索树,具备如下特征:就是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于其根节点的值;即任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值。

平衡二叉树:平衡二叉树首先是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。其实就是在二排序树的基础上进行限制,防止出现线性表的情况。

红黑树:红黑树是在平衡二叉树的基础上进行了优化(本质上也是一棵二叉排序树),以保存一个相对的平衡,而不是绝对的平衡,从而减少在插入和删除数据时旋转的次数(4种旋转方式)。Java种的TreeMap就是使用红黑树实现的,TreeSet又是基于TreeMap实现的,本质上也是对红黑树的实现。

以上就是常见的二叉树变种。除了二叉树外,还有一种在创建数据库索引中常用的树形 数据结构,b-tree以及其变种b+tree,后面有空再单独抽时间进行总结。本次总结再重点分析二叉树的遍历。

二叉树的深度优先遍历

二叉树的深度优先遍历分为三种:前序遍历、中序遍历、后续遍历。深度优先遍历跟栈有关,直接使用递归实现会比较简洁,如果使用循环实现,需要一个辅助栈。

前序遍历:先访问根结点,然后遍历左子树,最后遍历右子树。
public void preOrder (int index) {  
    if (datas[index] == null)  
        return;  
    System.out.print(datas[index] + " ");  //访问根节点
    preOrder(index*2);  //递归遍历左子树
    preOrder(index*2+1);  //递归遍历右子树
}
可以看出前序遍历的第一个节点其实就是跟节点。

中序遍历:先遍历左子树,再访问根节点,最后访问右子树。
public void midOrder (int index) {  
    if (datas[index] == null)  
        return;  
    midOrder (index*2);  //递归遍历左子树
    System.out.print(datas[index] + " ");  //访问根节点
    midOrder(index*2+1);  //递归遍历右子树
}
如果是二叉排序树,中序遍历的结果刚好是每个节点的顺序排序。

后序遍历:先遍历左子树,再遍历右子树,最后访问根节点。
public void postOrder (int index) {  
    if (datas[index] == null)  
        return;  
    postOrder (index*2);  //递归遍历左子树
postOrder(index*2+1);  //递归遍历右子树
System.out.print(datas[index] + " ");  //访问根节点
}
可以看出后续遍历的最后一个节点是根节点。
所有的深度优先遍历(无论是树、还是图),都需要借助一个栈进行记录和回溯到上一个节点。深度优先遍历的作用是找出所有路径。

二叉树的广度优先遍历

深度优先遍历需要借助栈,而广度优先遍历一般都需要借助队列(先进先出)来实现,采用循环从上到下一层一层的遍历即可。广度优先遍历,一遍用于查找最优路径或者最短路径。下面来看下二叉树的广度优先遍历实现:

void BreadthFirst(TreeNode root)
    {
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(queue.size()>0)
        {
            TreeNode node = queue.remove();
            System.out.println(root.val);//打印遍历节点


            if(node.left != null)
            {
                queue.offer(node.left);//把左孩子加入队列
            }
            if(node.right != null)
            {
                queue.offer(node.right);//把右孩子加入队列
            }
        }
}
class TreeNode{
    TreeNode left;
    TreeNode right;
    int val;
}


猜你喜欢

转载自blog.csdn.net/gantianxing/article/details/79863450