欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

二叉树基础-定义、创建、遍历、属性计算(深度,结点数)、查找算法

程序员文章站 2022-07-10 10:20:40
...
一、概念和定义   
    在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

    二叉树的每个结点至多只有二棵子树(不存在度大于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、结点的度和树的度
       树中每个结点具有的非空子树数或者说后继结点数被定义为该结点的度。树中所有结点的度的最大值被定义为该树的度
    2、分支节点和叶子结点
       度为0的为叶子结点或终端结点,度大于0的为分支节点。
    3、孩子结点、双亲结点、兄弟结点。
       在一棵树中,每个结点的子树的根或者说每个结点的后继,称为该结点的孩子,该结点称为孩子结点的双亲结点,具有同一个双亲结点的结点互称兄弟。结点的所有子树中的结点称为该结点的子孙。
    4、结点的层数和深度
三、二叉树的存储结构
1、顺序存储结构  顾名思义,以各结点编号为下标,把结点值对应存储到一维数组中。
   这种方式的好处,查找速度快,但是只适用于存放完全二叉树,对于存放单支结点比较多的二叉树会有很多存储空间空闲,浪费存储空间。这种结构适合存放完全二叉树或者单支结点较少的二叉树。
2、链式存储结构
   链表中的每个结点设置4个域:值域,左指针域,右指针域,双亲指针域。这样的结构即便于向上查找双亲结点,也方便查找该结点的孩子结点,缺点就是存储空间要比顺序存储结构大。
   结构定义代码实现:
package com.data.tree.po;

/**
 * 二叉树结点:数组元素结点
 * Created by xiaoyun on 2016/7/18.
 */
public class ArrayBTreeNode {
    private Object element;
    int left, right;
    public ArrayBTreeNode(Object obj) {
        this.element = obj;
    }
    public ArrayBTreeNode(Object obj, int lt, int rt) {
        this.element = obj;
        left = lt;
        right = rt;
    }
}

package com.data.tree.po;

/**
 * 二叉树节点:链式存储结构
 * Created by xiaoyun on 2016/7/18.
 */
public class BTreeNode {
    public Object element;
    public BTreeNode left,right;

    public BTreeNode(Object obj) {
        this.element = obj;
        left = right = null;
    }

    public BTreeNode(Object obj, BTreeNode lt, BTreeNode rt) {
        this.element = obj;
        this.left = lt;
        this.right = rt;
    }
}




四、常用算法及实现
1、根据二叉树的抽象数据类型,在Java中对应的接口定义为
package com.data.tree.po;

/**
 * 二叉树接口(标准行为规范定义)
 * Created by xiaoyun on 2016/7/19.
 */
public interface BinaryTree {
    // 由mode数组提供遍历二叉树的4中不同的访问次序(前序,中序,后续,层级)
    final String[] mode = {"preOrder", "inOrder", "postOrder", "levelOrder"};
    // 根据二叉树的广义表表示在计算机中建立对应的二叉树
    boolean createBTree(String gt);
    // 判断二叉树是否为空 为空,返回true,不为空,返回false
    boolean isEmpty();
    // 按照字符串s所给的次序遍历一棵二叉树,每个结点均被访问一次
    void traverseBTree(String s);
    // 从二叉树中查找值为obj的结点,若存在则返回完整值,否则返回空
    Object findBTree(Object obj);
    // 求出一棵二叉树的深度
    int depthBTree();
    // 求出一棵二叉树的结点数
    int countBTree();
    // 按照树的一种表示方法输出一棵二叉树
    void printBTree();
    // 清除二叉树的所有结点,使之变为一棵空树
    void clearBTree();
}

2、链接存储的二叉树定义
package com.data.tree.po;

import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;

/**
 * 链接二叉树(链接存储)
 * Created by admin on 2016/7/19.
 */
public class LinkBinaryTree implements BinaryTree {
    // 定义根节点
    protected BTreeNode root;

    // 无参数构造器
    public LinkBinaryTree() {
        this.root = null; // 二叉树的结点为空,即把空值null献给root
    }

    /**
     * 将一个二叉树的跟的引用赋值给root
     * @param root
     */
    public LinkBinaryTree(BTreeNode root) {
        this.root = root;
    }

    /**
     * 先序遍历
     * @param root
     */
    private void preOrder(BTreeNode root) {
        if (root != null) {
            System.out.print(root.element + " "); // 访问根节点
            preOrder(root.left); // 先序遍历左子树
            preOrder(root.right); // 先序遍历右子树
        }
    }

    /**
     * 中序遍历
     * @param root
     */
    private void inOrder(BTreeNode root) {
        if(root != null) {
            inOrder(root.left);
            System.out.print(root.element + " ");
            inOrder(root.right);
        }
    }

    /**
     * 后序遍历
     * @param root
     */
    private void postOrder(BTreeNode root) {
        if(root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.element + " ");
        }
    }

    /**
     * 层次遍历
     * @param root
     * 访问每个结点时,都需要经过入队和出队以及输出节点值的操作,这些操作的时间复杂度均为O(1),所以整个算法的时间复杂度为O(n)
     */
    private void levelOrder(BTreeNode root) {
        Queue queue = new ArrayDeque();
        BTreeNode p = null;
        queue.offer(root);// 将该树的根节点入队
        while (!queue.isEmpty()) {
            p = (BTreeNode) queue.poll();// 获取并移除该队列的头
            System.out.print(p.element + " ");
            if(p.left != null) {
                queue.offer(p.left);
            }
            if(p.right != null) {
                queue.offer(p.right);
            }
        }
    }

    /**
     * 查找的递归算法
     * @param root
     * @param x
     * @return
     * 思路:类似先序遍历
     *       1、如果树为空那么直接返回空
     *       2、比较根结点,如果相等直接返回根结点
     *       3、递归左子树,如果查找成功则返回结点的值
     *       4、递归查找右子树,如果查找成功则返回结点的值
     *       5、左右子树均为找到则返回null,查找失败
     */
    private Object findBTree(BTreeNode root, Object x) {
        if(root == null) {
            return null;
        }else {
            if(root.element.equals(x)) {
                return root.element;
            }else {
                Object target;
                target = findBTree(root.left, x);
                if(target != null) {
                    return target;
                }
                target = findBTree(root.right, x);
                if(target != null) {
                    return target;
                }
                return null;
            }
        }
    }

    /**
     * 求出二叉树深度的递归算法
     * @param root
     * @return
     * 思路:若一一棵二叉树为空,那么它的深度为0,否则它的深度等于左子树和右子树中的最大深度+1
     */
    private int depthBTree(BTreeNode root) {
        if(root == null) {
            return 0;
        }else {
            int dep1 = depthBTree(root.left);
            int dep2 = depthBTree(root.right);
            if(dep1 > dep2) {
                return dep1 + 1;
            }else {
                return dep2 + 1;
            }
        }
    }

    /**
     * 计算二叉树节点总数
     * @param root
     * @return
     * 思路:如果一棵二叉树为空,那么结点数为0,否则它等于左子树中的结点数与右子树中的结点数之和再加1
     */
    private int countBTree(BTreeNode root) {
        if(root == null){
            return 0;
        }else {
            int countLt = countBTree(root.left);
            int countGt = countBTree(root.right);
            return countLt + countGt + 1;
        }
    }

    /**
     * 输出二叉树广义表的递归算法
     * 说明:以广义表的形式输出一棵二叉树,应该首先输出根结点,然后再依次输出它的左子树和右子树,
     *       在输出左子树和之前答应左括号,输出右子树后打印右括号,具体广义表的输出规则,请查看数据结构之二叉树广义表的表示
     * @param root
     */
    private void printBTree(BTreeNode root) {
        if(root != null) {
            System.out.print(root.element);
            if (root.left != null || root.right != null) {
                System.out.print("(");
                printBTree(root.left);
                if(root.right != null) {
                    System.out.print(",");
                }
                printBTree(root.right);
                System.out.print(")");
            }
        }
    }

    /**
     * 建立二叉树的存储结构的方法
     * @param str
     * @return
     * 该算法的时间复杂度为O(n),n表示二叉树广义表中字符的个数,由于平均每两至三个字符之中就要一个元素字符,
     * 所以n也可以看做二叉树中元素结点的个数
     */
    public boolean createBTree(String str) {
        Stack stack = new Stack(); // 定义和创建一个保存结点指针的栈
        this.root = null; // 把树根指针置为空,即从空树开始
        BTreeNode p = null; // 定义p为指向二叉树结点的指针
        int k = 1;  // 用k作为处理结点的左子树和右子树的标记
        char[] a = str.toCharArray(); // 将字符串转换为字符数组
        for(int i = 0; i < a.length; i++) {
            switch (a[i]) {
                case ' ':
                    break;
                case '(':               // 处理左括号
                    stack.push(p);
                    k = 1;
                    break;
                case ')':              // 处理右括号
                    if(stack.isEmpty()){
                        System.out.println("二叉树广义表字符串错,返回假");
                        return false;
                    }
                    stack.pop();
                    break;
                case ',':
                    k = 2;
                    break;
                default:              // 扫描到的为字母,其他非法字符
                    if(a[i] >= 'a' && a[i] <= 'z' || a[i] >= 'A' && a[i] <= 'Z') {
                        p = new BTreeNode(a[i]);
                        if(root == null) {
                            root = p; // 将p结点定义为树根
                        }else { // 链接到双亲结点
                            if(k == 1) {
                                ((BTreeNode)stack.peek()).left = p;
                            }else {
                                ((BTreeNode)stack.peek()).right = p;
                            }
                        }
                    }else {
                        System.out.println("二叉树广义表中存在非法字符,返回假!");
                    }
            }
        }
        if(stack.isEmpty()) {
            return true;
        }else {
            return false;
        }
    }

    public boolean isEmpty() {
        return this.root == null;
    }

    /**
     * 按照一定的次序访问二叉树,使得每个结点均被访问一次
     * @param s
     */
    public void traverseBTree(String s) {
        if(s.equals(mode[0])){
            preOrder(root);
        }else if(s.equals(mode[1])) {
            inOrder(root);
        }else if(s.equals(mode[2])) {
            postOrder(root);
        }else if(s.equals(mode[3])) {
            levelOrder(root);
        }
        System.out.println(); // 输出换行
    }

    public Object findBTree(Object obj) {
        return findBTree(root, obj);
    }

    public int depthBTree() {
        return depthBTree(root);
    }

    public int countBTree() {
        return countBTree(root);
    }

    public void printBTree() {
        printBTree(root);
    }

    public void clearBTree() {
        root = null;
    }
}

3、测试类及运行结果
import com.data.tree.po.BinaryTree;
import com.data.tree.po.LinkBinaryTree;

/**
 * Created by admin on 2016/7/23.
 * 二叉树测试算法
 */
public class Example6_1 {
    public static void main(String[] args) {
        // 1、定义并创建链接存储结构的一棵空二叉树bt
        BinaryTree bt = new LinkBinaryTree();
        // 2、初始化二叉树广义表字符串
        String s = "A(B(C,D),E(,F(G)))";
        //String s = "a(b(c),d(e(f,g),h(,i)))";
        // 3、根据字符串建立链式存储结构的二叉树
        boolean yn = bt.createBTree(s);
        if (!yn) {
            System.out.println("表示二叉树的广义表字符串有误,请修正后在执行,错误的字符串s:" + s);
            System.exit(0);
        }
        // 4、广义表输出该二叉树
        System.out.println("二叉树的广义表表示形式:");
        bt.printBTree();
        // 5、前序遍历
        System.out.println("前序遍历:");
        bt.traverseBTree(BinaryTree.mode[0]);
        // 6、中序遍历
        System.out.println("中序遍历:");
        bt.traverseBTree(BinaryTree.mode[1]);
        // 7、后序遍历
        System.out.println("后序遍历:");
        bt.traverseBTree(BinaryTree.mode[2]);
        // 8、层级遍历
        System.out.println("层级遍历:");
        bt.traverseBTree(BinaryTree.mode[3]);
        // 9、求二叉树的深度
        System.out.println("计算该二叉树的深度:" + bt.depthBTree());
        // 10、计算该二叉树的结点数
        System.out.println("该二叉树的结点数:" + bt.countBTree());
        // 11、查找并输出二叉树中值为'C'和'F'的结点
        System.out.println("查找结点:" + bt.findBTree('c') + " " + bt.findBTree('F'));
        // 12、清空该二叉树
        bt.clearBTree();
    }
}

运行结果为:

二叉树的广义表表示形式:
A(B(C,D),E(,F(G)))前序遍历:
A B C D E F G
中序遍历:
C B D A E G F
后序遍历:
C D B G F E A
层级遍历:
A B E C D F G
计算该二叉树的深度:4
该二叉树的结点数:7
查找结点:null F

总结:本周学习和温习了二叉树的性质,定义,属性,基本术语,存储结构,常用算法。

忙碌的一周,导致今天才把二叉树的基本算法写完和理解。
二叉树的应用场景:搜索、索引、查询、排序等。
明天将学习二叉搜索树和堆。
下周计划:哈夫曼树、平衡二叉树,B树,红黑树了解,以及二叉树在软件、互联网和数据库中的应用。