第7章 二叉树

第7章 二叉树

1、二叉树的基本概念

  1. 二叉树:一个根结点及两颗互不相交的分别称做这个根结点的左子树和右子树的二叉树组成
  2. 二叉树与普通树的区别:二叉树的子树必定有序;普通树子树能够有序也能够无序
  3. 满二叉树:全部终端结点都位于同一层次,且其余非终端结点的度都为 \(2\)
  4. 彻底二叉树:一颗二叉树扣除其最大层次后为一颗满二叉树,且层次最大那层的全部结点都向左靠齐
  5. 注:满二叉树必定是彻底二叉树;彻底二叉树不必定是满二叉树
  6. 二叉树的性质:
    1. 一颗非空二叉树的第 \(i\) 层上至多有 \(2^{i-1}\) 个结点 (\(i>=1\)
    2. 深度为 \(h\) 的二叉树至多有 \(2^h-1\) 个结点(\(h>=1\)
    3. 对于任何一颗二叉树 \(T\),若是其终端结点(叶子结点)数为 \(n_0\),度为 \(1\) 的结点树为 \(n_1\),度为 \(2\) 的结点树为 \(n_2\),则 \(n_0=n_2+1\)
      1. 注:\(n_0+n_1+n_2 = n_1+2*n_2+1\)
    4. 对于具备 \(n\) 个结点的彻底二叉树,若是按照从
      上到下、同一层次上的结点按从左到右的顺序对二叉树中的全部结点从$ 1$ 开始顺序编号,则对于序号为 \(i\)
      结点,有:
      1. 若是 \(i>1\),则序号为 \(i\) 的双亲结点的序号为 \([i/2](取整函数)\)
      2. 若是 \(2*i>n\),则结点 \(i\) 无左子女(此时结点 \(i\) 为终端结点);不然其左子女为结点 \(2*i\)
      3. 若是 \(2*i+1>n\),则结点 \(i\) 无右子女;不然其右子女为节点 \(2*i+1\)

2、二叉树的基本运算

3、二叉树的存储结构

3.1 顺序存储结构

3.2 链式存储结构

3.2.1 二叉树链式存储结构

typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
    // struct node *parent; // 指向双亲的指针 (无关紧要)
} bintnode;
typedef bintnode *bintree;
bintree root; // 指向二叉树根结点的指针

4、二叉树的遍历

4.1 二叉树遍历的定义

  1. 前序遍历:首先访问根结点;
    而后按照前序遍历的顺序访问根结点的左子树;
    再按照前序遍历的顺序访问根结点的右子树node

  2. 中序遍历:首先按照中序遍历的顺序访问根结点的左子树;
    而后访问根结点;最后按照中序遍历的顺序访问根结点的右子树算法

  3. 后序遍历:首先按照后序遍历的顺序访问根结点的左子树;
    而后按照后序遍历的顺序访问根结点的右子树;最后访问根结点函数

  4. 图二叉树的遍历:spa

4.2 二叉树遍历的递归实现

  1. 二叉树遍历的递归实现 :按照遍历规定的次序,访问根结点时就输出根结点的值设计

  2. 经常使用操做:指针

    1. 中序遍历的二叉树的递归算法
    2. 后序遍历的二叉树的递归算法

4.2.1 前序遍历的二叉树的递归算法(算法)

typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

void preorder(bintree t) {
    if (t) {
        printf("%c", t->data);
        preorder(t->lchild);
        preorder(t->rchild);
    }
}

4.2.2 前序遍历时二叉树的建立算法(算法)

typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

bintree createbintree() {
    char ch;
    bintree t;
    if ((ch = getchar()) == '#') t = NULL;
    else {
        t = (bintnode *) malloc(sizeof(bintnode)); // 生成二叉树的根结点
        t->data = ch;
        t->lchild = createbintree(); // 递归实现左子树的创建
        t->rchild = createbintree(); // 递归实现右子树的创建
    }
    return t;
}
  1. 图建立二叉树:

4.3 二叉树遍历的非递归实现

  1. 经常使用操做:
    1. 中序遍历的二叉树的非递归算法
    2. 后序遍历的二叉树的非递归算法

4.3.1 前序遍历的二叉树的非递归算法(算法)

  1. 算法步骤:
    1. 对于一颗树(子树)\(t\)
    2. 访问完 \(t\) 的根结点值后,进入 \(t\) 的左子树,可是此时须要将 \(t\) 保存起来
    3. \(t\) 处设置一个回溯点
    4. 访问完左子树后,经过回溯点 \(t\) 进入 \(t\) 的右子树访问
    5. 注:栈顶元素即将出栈时,意味着根结点和左子树访问完成,出栈后须要进入其右子树访问
typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

void preorder1(bintree t) {
    seqstack s;
    s.top = 0;

    // 当前处理的子树不为空或栈不为空则循环
    while ((t) || (s.top != 0)) {
        if (t) {
            printf("%c ", t->data);
            push(&s, t);
            t = t->lchild;
        } else {
            t = pop(&s);
            t = t->rchlid;
        }
    }
}

5、二叉树其余运算的实现

5.1 二叉树的查找(算法)

  1. 算法步骤:
    1. 首先判断树是否为空
    2. 若是树(子树)结点值为 \(x\),则返回
      1. 不然前往左子树查找,找到返回值
        1. 不然前往右子树查找,找到返回值
typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

bintree locate(bintree t, dataype x) {
    bintree p;
    if (t == NULL) return NULL;
    else {
        if (t->data == x) return t;
        else {
            p = locate(t->lrchild);
            if (p) return p;
            else return locate(t->rchild)
        }
    }
}

5.2 统计二叉树中的结点的个数(算法)

  1. 算法步骤:
    1. 判断树(子树)是否为空
    2. 不为空递归查找左子树和右子树,而且返回左子树结点总数+右子树结点总数+根结点
typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int numofnode(bintree t) {
    if (t == NULL) return 0;
    else return (numofnode(t->lchild) + numofnode(t->rchild) + 1);
}

5.3 判断二叉树是否等价(算法)

  1. 算法步骤:
    1. 判断两个二叉树是否都为空,都为空则等价
    2. 若是两个二叉树不都为空
      1. 首先判断根结点是否相同
      2. 其次递归判断左子树是否相同
      3. 最后递归判断右子树是否相同,是否等价的标准取决于右子树是否也相同
typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int isequal(bintree t1, bintree t2) {
    int t;
    t = 0;
    if (t1 == NULL && t2 == NULL) t = 1; // t1 和 t2 均为空,则两者等价
    else {
        // 处理 t1 和 t2 均不为空的状况
        if (t1 != NUll && t2 != NULL)
            if (t1->data == t2->data) // 若是根结点的值相等
                if (isequeal(t1->lchild, t2->lchild)) // 若是 t1 和 t2 的左子树等价
                    t = isequeal(t1->rchild, t2->rchild); // 返回值取决于 t1 和 t2 的右子树是否等价
    }
    return (t);
}

5.4 求二叉树的高度(算法)

  1. 算法步骤:
    1. 首先处理空二叉树的状况
    2. 其次递归得出左子树的高度
    3. 最后递归得出右子树的高度
    4. 递归期间,若是左子树高度大于右子树高度,左子树高度加 \(1\),不然右子树高度加 \(1\)
    5. 注:递归出口应该用第三个变量 \(h\) 来返回子树高度
typedef char datatype; // 结点属性值的类型
// 二叉树结点的类型
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} bintnode;
typedef bintnode *bintree;

int depth(bintree t) {
    int h, lh, rh;
    if (t == NULL) h = 0; // 处理空二叉树的状况
    else {
        lh = depth(t->lchild); // 求左子树的高度
        rh = depth(t->rchild); // 求右子树的高度
        if (lh >= rh) h = lh + 1; // 求二叉树t的高度
        else h = rh + 1;
    }
    return h;
}

6、穿线二叉树

6.1 穿线二叉树的定义

  1. 穿线二叉树的指针:结点的左、右指针指向其左、右子女code

  2. 中序穿线二叉树的线索:结点的左、右指针指向其中序遍历的前驱、后继结点blog

  3. 为了区别结点的左右指针是指针仍是线索,通常加上 \(ltag\)\(rtag\) 两个标志位递归

    1. \(ltag=0\) 表示结点的左指针指向其左子女
    2. \(ltag=1\) 表示结点的左指针指向其中序遍历的前驱
    3. \(rtag=0\) 表示结点的右指针指向其右子女
    4. \(rtag=1\) 表示结点的右指针指向其中序遍历的后继
  4. 图中序穿线二叉树:get

6.2 中序穿线二叉树的基本运算

6.3 中序穿线二叉树的存储结构及其实现

  1. 经常使用操做:
    1. 建立一颗中序二叉树

6.3.1 中序穿线二叉树的存储结构

typedef char datatype;
typedef struct node {
    datatype data;
    int ltag, rtag; // 左右标志位
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *binthrtree;

6.3.2 中序遍历中序穿线二叉树(真题)(算法)

  1. 算法步骤:
    1. 找到中序遍历下的第一个结点(从根结点出发,沿着左指针不断往左走,直至左指针为空)
    2. 从中序遍历的第一个结点开始,不断地寻找当前结点在中序遍历下的后继结点并输出
      1. 在中序穿线二叉树中找后继结点步骤:
        1. 若右标志为 \(1\),则代表右指针正好指向其中序遍历下的后继结点
        2. 若右标志为 \(0\),则说明他有右子树,所以其中序遍历下的后继结点应该是该右子树中序遍历下的第一个结点(右子树的最左下的结点,与第一步步骤彻底相同)
typedef char datatype;
typedef struct node {
    datatype data;
    int ltag, rtag; // 左右标志位
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *binthrtree;

// 寻找结点 p 在中序遍历下的后继结点
binthrtree insuccnode(binthrtree p) {
    binthrtree q;
    if (p->rtag == 1) // p 的右指针为线索,恰巧指向p的后继结点
        return p->rchild;
    else {
        q = p->rchild; // 寻找 p 的右子树中最左下的结点
        while (q->ltag == 0) q = q->lchild; // 求该右子树下中序遍历下的第一个结点
        return q;
    }
}

// 中序遍历中序穿线二叉树
void inthrtree(binthrtree p) {
    if (p) {
        while (p->ltag == 0) p = p->lchild; // 求 p 中序遍历下的第一个结点
        do {
            printf("%c ", p->data);
            p = insuccnode(p); // 求 p 中序遍历下的后继结点
        } while (p);
    }
}

7、树、森林和二叉树的转换

  1. 注:任意一颗树(森林)都惟一地对应一颗二叉树;相反,任何一颗二叉树都惟一地对应一颗树(森林)

7.1 树、森林到二叉树的转换

  1. 树、森林到二叉树的转换步骤

    1. 在全部兄弟结点之间添加一条连线,若是是森林,则在其全部树的树根之间一样也添加一条连线
    2. 对于树、森林中的每一个结点,除保留其到第一个子女的连线外,撤消其到其它子女的连线;
    3. 将以上获得的树按照顺时针方向旋转45度
  2. 图树到二叉树的转换:

  3. 图森林到二叉树的转换:

7.2 二叉树到树、森林的转换

  1. 首先将二叉树按照逆时针方向旋转45度

  2. 若某结点是其双亲的左子女,则把该结点的右子女,右子女的右子女,……都与该结点的双亲用线连起来

  3. 最后去掉原二叉树中全部双亲到其右子女的连线

  4. 注:最后链接子结点,只能链接右子女,而不能链接左子女

  5. 图二叉树到森林的转换:

8、经过前中后序遍历肯定二叉树(补充)

8.1 前序+中序遍历

  1. 已知一棵二叉树的前序和中序序列,构造该二叉树的过程以下:
    1. 根据前序序列的第一个元素创建根结点;

    2. 在中序序列中找到该元素,肯定根结点的左右子树的中序序列;

    3. 在前序序列中肯定左右子树的前序序列;

    4. 由左子树的前序序列和中序序列创建左子树;

    5. 由右子树的前序序列和中序序列创建右子树。

如:已知一棵二叉树的先序遍历序列和中序遍历序列分别是 abdgcefh、dgbaechf,求二叉树及后序遍历序列。

先序:abdgcefh—>a bdg cefh
中序:dgbaechf—->dgb a echf
得出结论:a 是树根,a 有左子树和右子树,左子树有 bdg 结点,右子树有 cefh 结点

先序:bdg—>b dg
中序:dgb —>dg b
得出结论:b 是左子树的根结点,b 无右子树,有左子树

先序:dg—->d g
中序:dg—–>dg
得出结论:d 是 b 左子树的根节点,d 无左子树,g 是 d 的右子树

而后对于 a 的右子树相似能够推出

而后还原

8.2 中序+后序遍历

  1. 已知一棵二叉树的中序和后序序列,构造该二叉树的过程以下:
    1. 根据后序序列的最后一个元素创建根结点
    2. 在中序序列中找到该元素,肯定根结点的左右子树的中序序列
    3. 在后序序列中肯定左右子树的后序序列
    4. 由左子树的后序序列和中序序列创建左子树
    5. 由右子树的后序序列和中序序列创建右子树
如:已知一棵二叉树的后序遍历序列和中序遍历序列分别是gdbehfca、dgbaechf,求二叉树

后序:gdbehfca—->gdb ehfc a
中序:dgbaechf—–>dgb a echf
得出结论:a是树根,a有左子树和右子树,左子树有 bdg 结点,右子树有 cefh 结点

后序:gdb—->gd b
中序:dgb—–>dg b
得出结论:b 是 a 左子树的根节点,无右子树,有左子树 dg

后序:gd—->g d
中序:dg—–>d g
得出结论:d 是 b 的左子树根节点,g 是 d 的右子树

而后对于 a 的右子树相似能够推出

而后还原

8.3 前序+后序遍历

  1. 注:前序和后序在本质上都是将父节点与子结点进行分离,但并无指明左子树和右子树的能力,所以获得这两个序列只能明确父子关系,而不能惟一地肯定一棵二叉树

9、算法设计题

9.1 求一颗给定二叉树中叶子结点的个数(递归和非递归)(真题)(算法)

分别采用递归和非递归方式编写两个函数,求一棵给定二叉树中叶子结点的个数

  1. 算法步骤(递归):
    1. 判断二叉树是否为空,为空返回 \(0\)
    2. 判断二叉树的子树(子树的子树)的两个左右孩子是否为空,为空返回 \(1\)
    3. 左右递归搜索两棵子树
  2. 算法步骤(非递归):
    1. 二叉树不为空或栈不为空则开启循环
      1. 若是二叉树存在,而且左右子树为空,计数加 \(1\)
        1. 不然将该子树放入栈中,而且搜索该子树的左子树
      2. 若是二叉树不存在,证实左子树搜索完毕,从栈中取出子树搜索其右子树
typedef char datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *bintree;

// 递归方法求二叉树叶子结点的个数
int leaf1(bintree t) {
    if (t == NULL) return 0;
    else if (!t->lchild && !t->rchild)
        return 1;
    else
        return leaf1(t->lchild) + leaf1(t->rchild);
}

// 非递归方法求二叉树叶子结点的个数
int leaf2(bintree t) {
    seqstack s; // 顺序栈
    int count = 0; // 叶子结点计数变量
    init(&s); // 初始化空栈

    while (t || !empty(&s)) {
        if (t) {
            if (!t->lchild && !t->rchild) count++;
            push(&s, t);
            t = t->lchild;
        } else {
            t = pop(&s);
            t = t->rchild;
        }
    }
    return count;
}

9.2 返回一颗给定二叉树在中序遍历下的最后一个结点(真题)(算法)

试编写一个函数,返回一颗给定二叉树在中序遍历下的最后一个结点

  1. 算法 步骤:
    1. 不断地查找右子树的右子结点
    2. 返回最后一个右子结点
typedef char datatype;
typedef struct node {
    datatype data;
    struct node *lchild, *rchild;
} binthrnode;
typedef binthrnode *bintree;

// 递归实现
bintree midlast(bintree t) {
    if (t && t->rchild) t = midlast(t->rchild);
    return t;
}

// 非递归实现
bintree midlast(bintree t) {
    bintree p = t;
    while (p && p->rchild) p = p->rchild;
    return p;
}

10、错题集

  1. 前序(根左右)和中序(左根右)遍历结果相同的二叉树(去掉左都为“根右”)为(全部结点只有右子树的二叉树);前序(根左右)和后序(左右根)遍历结果相同的二叉树(哪个子树都不能够去掉)为(只有根结点的二叉树)

  2. \(n\) 个结点的二叉树,已知叶结点个数为 \(n_0\),则该树中度为 \(1\) 的结点的个数为(\(n-2*n_0+1\));若此树是深度为 \(k\) 的彻底二叉树,则 \(n\) 的最小值为 (\(2^{k-1}\)

    1. 注:彻底二叉树的最后一层,必定有一个结点,所以 \(n\) 的最小值为 \(k-1\) 层总结点数加 \(1\)\(2^{k-1}-1+1\)
  3. (真题)对于一颗具备 \(n\) 个结点的二叉树,该二叉树中全部结点的读书之和为(\(n-1\)

  4. (真题)试分别画出具备 \(3\) 个结点的树和具备 \(3\) 个结点的二叉树的全部不一样形态。(此题注意树具备无序性)

    1. 图例题7-9答案
相关文章
相关标签/搜索