算法基础11 —— 树入门(二叉树的遍历以及构造 + 普通树转换成二叉树 + 例题 + 二叉树的一些操作)

本文详细介绍了字符二叉树的先序、中序、后序和层序遍历算法,并通过代码实现。此外,还展示了如何根据先序和中序序列构造二叉树,以及一系列关于二叉树操作的练习题目,包括结点个数、叶子数、深度计算和判断等。
字符二叉树的遍历

在这里插入图片描述
对于以上二叉树
先序遍历为(根左右) : ABCDEFG
中序遍历为(左根右) : CBEDAFG
后序遍历为(左右根) : CEDBGFA
层序遍历:(从左往右、从上往下) ABFCDGE

以先序次序输入以上二叉树:
ABC##DE###F#G##

代码实现遍历操作:

#include <iostream>
#include <queue>
using namespace std;

typedef struct node
{
    char data;
    struct node *lchild;
    struct node *rchild;
}BTNode,*tree;
tree root = NULL;

//按照先序次序输入二叉树的结点,bt为指向根结点的指针,建树需要&
void pre_crt(tree &bt)
{
    char ch;
    ch = getchar();
    if (ch != '#')
    {
        bt = new node;//建立根结点
        bt -> data = ch;
        pre_crt(bt -> lchild);//建左子树
        pre_crt(bt -> rchild);//建右子树
    }
    else bt = NULL;
}

void DLR(tree bt)
{
    if (bt == NULL) return;
    
    //先序遍历(根左右),遍历树不需要&
    cout << bt -> data;
    DLR(bt -> lchild);
    DLR(bt -> rchild);
}

void LDR(tree bt)
{
    if (bt == NULL) return;
    
    //中序遍历(左根右)
    LDR(bt -> lchild);
    cout << bt -> data;
    LDR(bt -> rchild);
}

void LRD(tree bt)
{
    if (bt == NULL) return;
    
    //后序遍历(左右根)
    LRD(bt -> lchild);
    LRD(bt -> rchild);
    cout << bt -> data;
}

//层次遍历,传入根结点
void BTLeversearch(tree bt)
{
    queue<tree> q;//tree指的是指针,即队列中存储指针
    if (bt == NULL) return;
    q.push(bt);//根结点入队
    while (!q.empty())//队列非空
    {
        tree head = q.front();//获取队头元素
        cout << head -> data;
        if (head -> lchild) q.push(head -> lchild);
        if (head -> rchild) q.push(head -> rchild);
        q.pop();
    }
}

int main()
{
    pre_crt(root);//创建
    DLR(root);//先序遍历
    cout << endl;
    LDR(root);//中序遍历
    cout << endl;
    LRD(root);//后序遍历
    cout << endl;
    BTLeversearch(root);
    cout << endl;
    return 0;
}

输入
ABC##DE###F#G##
输出
ABCDEFG
CBEDAFG
CEDBGFA
ABFCDGE

二叉树的构造

已知结点的先序序列为ABDGEHCF,中序序列为GDBHEACF。试构造出二叉树。
方法: 如果已知一棵二叉树的先序或者后序序列,就可以首先确定根结点(第一个或者最后一个结点),再根据中序序列确定左右子树如何分布。对于每棵子树,反复的从先序或者后序序列中找出根结点… …
【对于上例,二叉树的根结点为A】
构造出的二叉树如下:
在这里插入图片描述

普通树转换成二叉树

二叉树的操作和应用更广泛,在实际使用时,我们经常把普通树转换成二叉树进行操作。如何转换呢?一般方法如下:

  • 第一步:将树中每个结点除了最左边的一个分支保留外,其余分支都去掉;
  • 第二步:从最左边结点开始画一条线,把同一层上的兄弟结点都连起来;
  • 第三步:以整棵树的根结点为轴心,将整棵树顺时针旋转45度。

举例:
下图是一棵普通的树
在这里插入图片描述

经过第一步之后,树变为如下形态:
在这里插入图片描述

经过第二步之后,树变为如下形态:
注意:只连接兄弟结点即可,故六号结点和七号结点不需要连接
在这里插入图片描述

经过第三步之后,树变为如下二叉树形态:
在这里插入图片描述

几道例题

例1.一棵完全二叉树的结点总数为18,其叶结点数为©
A.7
B.8
C.9
D.10
解:对于n层二叉树,其最多有2^n - 1个结点。由2 ^ n - 1 = 18,2 ^ 4 - 1 = 15 < 18,2 ^ 5 - 1 = 31 > 18,故n等于5,5层二叉树画图即可。不难发现,有9个叶子结点
在这里插入图片描述
例2.对任何一棵二叉树T,设n0、n1、n2分别是度数为0、1、2的顶点数,则下列判断中正确的是 (A)
A.n0 = n2+1
B.n1 = n0+1
C.n2 = n0+1
D.n2 = n1+1
送分题:性质的复习

例3.某二叉树的前序序列和后序序列正好相反,则该二叉树一定是 (B)的二叉树。
A.空或只有一个结点
B.高度等于其结点数
C.任一结点无左孩子
D.任一结点无右孩子
解法一:已知前序序列和后序序列相反

  • 若去掉左子树,DLR=DR,LRD=RD,满足条件
  • 若去掉右子树,DLR=DL,LRD=LD,满足条件
  • C、D中带有一定二字,故排除C、D,A可以直接排除

解法二:对于下面的两棵二叉树,其先序遍历和后序遍历序列均相反,故C、D均可排除。但其都满足高度等于其结点数,故选B
在这里插入图片描述
先序遍历:1234
后序遍历:4321
在这里插入图片描述
先序遍历:1234
后序遍历:4321

例4.一个二叉树的先序遍历结果和中序遍历结果相同,则其所有非叶子节点必须满足的条件是(B)
A.只有左子树
B.只有右子树
C.节点的度为1
D.节点的度为2
解:
与上题类似,已知先序遍历和中序遍历相同,即DLR=LDR

  • 若去掉右子树(只有左子树),则DL=LD,先序遍历和中序遍历相反,故排除A
  • 若去掉左子树(只有右子树),则DR=DR,先序遍历和中序遍历相同,故选B

例5.下图为一个二叉树,请选出以下不是遍历二叉树产生的顺序序列的选项(BD)
A. ABCDEFIGJH
B. BDCAIJGHFE
C. BDCAIFJGHE
D. DCBJHGIFEA
在这里插入图片描述
解:
先序序列(根左右):ABCDEFIGJH
中序序列(左根右):BDCAIFJGHE
后序序列(左右根):DCBIJHGFEA

例6.中缀表达式A-(B+C/D)*E的后缀形式是(D)
A.AB-C+D/E*
B.ABC+D/-E*
C.ABCD/E*+-
D.ABCD/+E*-
提示:后缀表达式的操作符在操作数的后面

例7.中缀表达式1-(2+3)的前缀形式是(A)
A.-1+23
B.-12+3
C.-12+3
D.-+123
提示:前缀表达式的操作符在操作数的前面

二叉树的一些操作
  • 求二叉树结点的个数
  • 求二叉树叶子结点的个数
  • 求二叉树的深度
  • 判断一个结点是否在二叉树中
  • 求第k层结点的个数

代码实现:

#include <iostream>
#include <queue>
using namespace std;

typedef struct node
{
    char data;
    struct node *lchild;
    struct node *rchild;
}BTNode,*tree;
tree root = NULL;

//先序次序建树
void pre_crt(tree &bt)
{
    char ch;
    ch = getchar();
    if (ch != '#')
    {
        bt = new node;
        bt -> data = ch;
        pre_crt(bt -> lchild);
        pre_crt(bt -> rchild);
    }
    else bt = NULL;
}

//二叉树结点的个数
//二叉树结点的个数=左子树结点个数+右子树结点个数+1 (1为根结点)
int BTNodesize(tree bt)
{
    if (bt == NULL) return 0;
    return BTNodesize(bt -> lchild) + BTNodesize(bt -> rchild) + 1;
}

//二叉树叶子结点的个数
//二叉树叶子结点的个数=左子树叶子结点个数+右子树叶子结点个数
int BTNodeleaf(tree bt)
{
    if (bt == NULL) return 0;
    if (bt -> lchild == NULL && bt -> rchild == NULL) return 1;//叶子结点
    return BTNodeleaf(bt -> lchild) + BTNodeleaf(bt -> rchild);
}

//求二叉树的深度
//二叉树的深度=max(左子树深度,右子树深度)+1 (1为根结点)
int BTDepth(tree bt)
{
    if (bt == NULL) return 0;
    int left = BTDepth(bt -> lchild);
    int right = BTDepth(bt -> rchild);
    return (left > right) ? (left + 1):(right + 1);
}

//判断一个结点x是否在二叉树中
bool BTFind(tree bt,char x)
{
    bool temp;
    if (bt == NULL) return false;//树空,返回false
    if (bt -> data == x) return true;//递归出口:找到
    temp = BTFind(bt -> lchild,x);//从左子树中查找x
    if (temp) return true;
    temp = BTFind(bt -> rchild,x);//从右子树中查找x
    if (temp) return true;
    
    return false;//没找到
}

//求第k层结点的个数
//当前树的第k层等于当前树的左子树的第k-1层(右子树同理)
int BTNodeKLevelSize(tree bt,int k)
{
    if (bt == NULL) return 0;//空树
    if (k == 1) return 1;//第一层之有根结点
    return BTNodeKLevelSize(bt -> lchild,k - 1) + BTNodeKLevelSize(bt -> rchild,k - 1);
}

int main()
{
    pre_crt(root);
    cout << BTNodesize(root) << endl;//求二叉树结点的个数:7
	cout << BTNodeleaf(root) << endl;//求二叉树叶子结点的个数:3
	cout << BTDepth(root) << endl;//求二叉树的深度:4
	
	if (BTFind(root,'D')) cout << "FIND" << endl;
	else cout << "NOT FIND" << endl;//判断一个结点是否在二叉树中
	
	cout << BTNodeKLevelSize(root,3) << endl;//求第3层结点的个数3:
    return 0;
}

(以博客开头的第一棵树为例)

输入
ABC##DE###F#G##
输出
7
3
4
FIND
3

判断一颗二叉树是否为完全二叉树

算法思想:

  • 借助queue< int> q,将各个结点以层序遍历序列进行入队
  • 如果发现q.front()为NULL时,表示读取到了一个空结点,若它后面的结点都是NULL,则表示为完全二叉树
  • 若它后面的结点出现不为NULL的情况,表示为非完全二叉树。

举例:
对于下图的一棵完全二叉树
在这里插入图片描述
对其进行层序遍历,得到1-2-5-3-4-null-null,按照这个顺序将结点依次入队,得到队列:
在这里插入图片描述
队列中的非NULL结点连续,故其是一棵完全二叉树

对于下图的非完全二叉树
在这里插入图片描述
对其进行层序遍历,得到1-2-5-3-4-null-6,按照这个顺序将结点依次入队,得到队列:
在这里插入图片描述
队列中的非NULL结点不连续,故其是一棵非完全二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值