二叉树(binary tree)

二叉树的定义####

二叉树是n(n>=0)个具有相同类型的元素的有限集合,当n=0时称为空二叉树,当n>0时,数据元素被分为一个称为根(Root)的数据元素及两棵分别为左子树和右子树的数据元素的集合,左、右子树互不相交,且左、右子树都为二叉树。在二叉树中,一个元素也称为一个节点。

二叉树的子树是有序的,即若将其左、右子树颠倒,就将成为另一棵不同的二叉树。即使树中的节点只有一棵子树,也要明确指出其是左子树还是右子树。由于左、右子树的有序以及二叉树可以为空,因此,二叉树具有以下五种基本形态,即空二叉树、仅有根节点的二叉树、右子树为空的二叉树、左子树为空的二叉树、左右子树均为非空的二叉树,如下图所示:

二叉树的五种基本情形

二叉树的基本概念####

  1. 节点的度:节点所拥有子树的个数称为该节点的度。
  2. 叶子:度为0的节点称为叶子。
  3. 孩子:节点子树的根,称为该节点的孩子。二叉树中,孩子有左右之分,分别为左孩子和右孩子。
  4. 双亲:孩子节点的上层节点都称为该节点的双亲。
  5. 以某节点的根的子树中的除根节点之外的任意一个节点都称为该节点的子孙。
  6. 祖先:节点的祖先是从根到该节点所经分支上的所有节点。
  7. 节点的层次:从根节点起,跟为第一层,他的孩子为第二层,孩子的孩子为第三层,依次类推,即某个节点的层次为L层,那么他的孩子节点的层数为L+1层。
  8. 兄弟:同一双亲的孩子互为兄弟。
  9. 堂兄弟:其双亲在同一层的节点互为堂兄弟。
  10. 二叉树的度:二叉树中最大的节点度称为二叉树的度。
  11. 二叉树的深度:二叉树中节点的最大层次书被称为二叉树的深度。
  12. 满二叉树:在一棵二叉树中,所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,这样的二叉树被称为满二叉树。
  13. 完全二叉树:对深度为k的满二叉树中的节点从上至下、从左至右从1开始编号。对一棵具有n个节点、深度为k的二叉树,采用的同样的办法对树中的节点从上至下,从左至右从1开始连续编号,如果编号为i(i<=n)的节点与满二叉树中编号为i的节点在同一位置,则称此二叉树为完全二叉树。对于一棵完全二叉树,其叶子节点只可能出现在最下层和倒数第二层,而最下层的叶子集中在树的最左部。
满二叉树和完全二叉树

二叉树的性质####

  1. 一棵非空二叉树的第i层最多有2^(i-1)个节点。
  2. 深度为k的二叉树至多有2^k-1个节点。
  3. 对任何一棵二叉树T,如果叶子节点数为n0,度为2的节点数为n2,那么n0=n2+1。
  4. 具有n个节点的安全二叉树的深度k=log2N+1(简书不支持公式。。。这里解释一下,k=以2为底N的对数向下取整+1)。
  5. 对一棵具有n个节点的完全二叉树的从上至下,从左至右从1开始连续编号,那么对任意一个节点i有:
    如果i=1,则节点i是二叉树的根,无双亲;如果i>1,则其双亲是i/2向下取整。
    如果2i>n,则节点无左孩子,为叶子节点;如果2i<n,则其左孩子是2i。
    如果2i+1>n,则节点i无右孩子;如果2i+1<=n,则其右孩子为2i+1。
性质5

二叉树的存储结构####

顺序存储######

完全二叉树的顺序存储
我们先看以下完全二叉树的顺序存储。要存储一棵完全二叉树,不仅需要二叉树的节点数据,还需要存储它的结构信息,即双亲和孩子关系信息。从上面的性质5可以看出,完全二叉树中节点i的左孩子为2i,右孩子为2i+1,双亲为i/2向下取整。因此,如果将完全二叉树从上至下,从左至右从1开始编号,把编号为i的节点放在线性存储单元的第i个位子,那么其左孩子存储在2i位子,右孩子存储在2i+1位子,则孩子和双亲的关系就体现出来了。

完全二叉树的顺序存储

一般二叉树的顺序存储
对于一般二叉树而言,如果把它的节点按照从上至下、从左至右从1开始连续编号存储在一维的存储单元,那么无法反应其节点间的双亲孩子关系,即不能反映二叉树的结构关系,怎么办呢?可以把一般的二叉树补成一棵完全二叉树,这样,它就可以按照完全二叉树的顺序存储方式存储了,只是新补上去的节点只占位子,不存放节点数据。如下图所示:

一般二叉树的顺序存储

对于一般二叉树,需要增加一些节点才能存储此二叉树的节点数据和结构关系,这样可能会造成存储空间的浪费,例如,对于深度为3的右偏斜二叉树,需要额外增加4个存储单位。当二叉树的深度更深时,则需要更多的节点,例如当深度为100的右偏斜二叉树,需要2^100-101个额外空间,为了采用顺序存储方式来存储此二叉树,把全世界所有计算机的存储空间加起来也不够!因此很有必要采用其他形式的存储方式。

右偏斜二叉树的顺序存储
链式存储######

链表可以用来表示一维的线性结构,也可以用来表示非线性的二叉树结构。二叉树的链式存储结构常有二叉链表存储、三叉链表存储及线索链表。二叉链表中有两个指针域,分别指向其左、右孩子;三叉链表中除了指向其左、右孩子的指针域外,还有指向其双亲的指针域;线索链表是为了反映节点的前驱、后继而将二叉链表中空指针指向其前驱或后继而形成的链式存储结构。

二叉链表和三叉链表的结构

二叉链表存储
链表中每一个节点包含3个域:数据域、左孩子指针域、右孩子指针域。左、右孩子指针域分别指向左孩子和右孩子的存储地址。

二叉链表存储
二叉链表存储代码实现######
public class BinaryTreeNode<T> {
    private T data;
    private BinaryTreeNode<T> leftChild;
    private BinaryTreeNode<T> rightChild;

    public BinaryTreeNode(T data) {
        this(data, null, null);
    }

    public BinaryTreeNode(T data, BinaryTreeNode<T> leftChild, BinaryTreeNode<T> rightChild) {
        this.leftChild = leftChild;
        this.rightChild = rightChild;
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public BinaryTreeNode<T> getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(BinaryTreeNode<T> leftChild) {
        this.leftChild = leftChild;
    }

    public BinaryTreeNode<T> getRightChild() {
        return rightChild;
    }

    public void setRightChild(BinaryTreeNode<T> rightChild) {
        this.rightChild = rightChild;
    }

}

三叉链表存储
在三叉链表中,除根节点的parent域为空外,其余节点的parent域都不为空,指向其双亲。因此在三叉链表中,查找孩子和双亲都是很快捷的,但是增加了一些额外空间开销。

三叉链表存储结构
三叉链表存储实现#####
public class BinaryTreeNode3<T> {
    private T data;
    private BinaryTreeNode3<T> leftChild;
    private BinaryTreeNode3<T> parent;
    private BinaryTreeNode3<T> rightChild;

    // 叶子节点构造器
    public BinaryTreeNode3(T data, BinaryTreeNode3<T> parent) {
        this(data, null, parent, null);
    }

    // 一般节点构造器
    public BinaryTreeNode3(T data, BinaryTreeNode3<T> leftChild, BinaryTreeNode3<T> parent,
            BinaryTreeNode3<T> rightChild) {
        this.leftChild = leftChild;
        this.parent = parent;
        this.rightChild = rightChild;
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public BinaryTreeNode3<T> getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(BinaryTreeNode3<T> leftChild) {
        this.leftChild = leftChild;
    }

    public BinaryTreeNode3<T> getParent() {
        return parent;
    }

    public void setParent(BinaryTreeNode3<T> parent) {
        this.parent = parent;
    }

    public BinaryTreeNode3<T> getRightChild() {
        return rightChild;
    }

    public void setRightChild(BinaryTreeNode3<T> rightChild) {
        this.rightChild = rightChild;
    }

}

二叉树的遍历####

先序、中序、后续的递归遍历######

例子

遍历的二叉树

代码

public class BinaryTree<T> {

    public BinaryTree() {
        // ...
    }

    // 访问数据
    public void visitData(BinaryTreeNode<T> node) {
        System.out.print(node.getData() + " ");
    }

    // 前序遍历
    public void preOrder(BinaryTreeNode<T> node) {
        if (null != node) {
            visitData(node);
            preOrder(node.getLeftChild());
            preOrder(node.getRightChild());
        }
    }

    // 中序遍历
    public void inOrder(BinaryTreeNode<T> node) {
        if (null != node) {
            inOrder(node.getLeftChild());
            visitData(node);
            inOrder(node.getRightChild());
        }
    }

    // 后续遍历
    public void postOrder(BinaryTreeNode<T> node) {
        if (null != node) {
            postOrder(node.getLeftChild());
            postOrder(node.getRightChild());
            visitData(node);
        }
    }

    public static void main(String[] args) {
        BinaryTreeNode<String> g = new BinaryTreeNode<String>("g");
        BinaryTreeNode<String> c = new BinaryTreeNode<String>("c");
        BinaryTreeNode<String> d = new BinaryTreeNode<String>("d");
        BinaryTreeNode<String> b = new BinaryTreeNode<String>("b", c, d);
        BinaryTreeNode<String> f = new BinaryTreeNode<String>("f", g, null);
        BinaryTreeNode<String> e = new BinaryTreeNode<String>("e", null, f);
        BinaryTreeNode<String> a = new BinaryTreeNode<String>("a", b, e);

        BinaryTree<String> binaryTree = new BinaryTree<String>();

        System.out.print("preOder:");
        binaryTree.preOrder(a);
        System.out.println();

        System.out.print("inOder:");
        binaryTree.inOrder(a);
        System.out.println();

        System.out.print("postOder:");
        binaryTree.postOrder(a);
        System.out.println();

    }
}

执行结果

递归遍历的结果
非递归遍历的思想######

当非递归遍历时,我们需要从根节点开始,从左到右深入到每一个叶子。当深入到一个叶子节点时,需要返回到其父节点,然后去深入其他分支。可以看出深入和返回是一对相反的操作,所以可以用到数据结构 来保存深入节点时树的结构关系。至于该遍历是先序、中序或是后序,这只取决于其访问

非递归的中序遍历######

沿左子树深入时,深入一个节点入栈一个节点,沿左分支无法继续深入时(某个节点无左孩子),出栈,出站时同时访问节点数据,然后从该节点的右子树继续沿左子树深入,这样一直下去,最后从根节点的右子树返回时结束。

    // 非递归的中序遍历
    public void nrInOrder(BinaryTreeNode<T> root) {
        // 声明一个栈
        Stack<BinaryTreeNode<T>> stack = new Stack<BinaryTreeNode<T>>();
        // 当前节点
        BinaryTreeNode<T> p = root;
        // 当节点和栈不同时为空时
        while (!(p == null && stack.isEmpty())) {
            // 遍历p节点下的所有左孩子
            while (p != null) {
                // 将左孩子压栈
                stack.push(p);
                p = p.getLeftChild();
            }
            if (stack.isEmpty()) {
                return;
            } else {
                p = stack.pop();// 出栈
                visitData(p);// 出栈时访问数据
                p = p.getRightChild();// 指向右孩子
            }

        }
    }
非递归的层次遍历######

从根节点开始,根节点入队,访问其数据,然后根节点的左右孩子入队,根节点出队。此时相当于第一层遍历完毕。第二层数据已经入队。然后当前队首元素出队,访问数据,加入当前元素的左右孩子,依次类推直到队列为空。

public void levelOrder(BinaryTreeNode<T> root) {
        // 声明一个队列
        Queue<BinaryTreeNode<T>> queue = new LinkedList<BinaryTreeNode<T>>();
        // 如果二叉树为空,直接返回
        if (null == root) {
            return;
        }
        // 根节点入队
        queue.add(root);
        // 临时变量,用来保存上一次出队的元素
        BinaryTreeNode<T> temp;
        // 遍历队列,直到队列为空
        while (!queue.isEmpty()) {
            // 出队
            temp = queue.remove();
            // 访问刚才出队元素的数据
            visitData(temp);
            // 若刚才出队的元素(节点)有左孩子,则左孩子入队
            if (null != temp.getLeftChild()) {
                queue.add(temp.getLeftChild());
            }
            // 若刚才出队的元素(节点)有左孩子,则左孩子入队
            if (null != temp.getRightChild()) {
                queue.add(temp.getRightChild());
            }
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,432评论 1 31
  • 树是n(n >= 0)个结点的有限集。n=0时称为空树。在任意一颗非空树中: 1、有且仅有一个特定的称为根(Roo...
    jtsky阅读 883评论 0 0
  • source Description A binary tree is a finite set of verti...
    Gitfan阅读 305评论 0 0
  • 什么是”树“ 树(tree)是包含n(n>0)个结点的有穷集,其中:(1)每个元素称为结点(node)(2)有一个...
    Neo_joke阅读 1,178评论 1 5
  • 昨天玩荣耀忘记写了,没什么事。今天妈又托人带了鱼来,婆婆宰的时候割到手,剩下的我宰了,一点点伤口却惊天动地,晚上...
    五十度浪浪阅读 139评论 0 0