数据结构(14)二叉树的模拟实现和便利代码

1.二叉树概念

二叉树是什么?① 空树 ② 非空:根节点、根节点的左子树与根节点的又子树组成的。

从概念中我们不难看出,二叉树的定义是递归式的。因此后续基本操作中,基本都是按照该概念来实现的,来看 A 的左子树,把 B 看作为根节点,又是颗二叉树。所以可以通过采用递归的手法来实现二叉树。


2.二叉树定义

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<stdio.h>
 
typedef char BTDataType;
 
typedef struct BinaryTreeNode 
{
    struct BinaryTreeNode* left;       // 记录左节点
    struct BinaryTreeNode* right;      // 记录右节点
    BTDataType data;                   // 存储数据
} BTNode;
 
//创建新节点
BTNode* CreateNode(BTDataType x) 
{
    BTNode* new_node = (BTNode*)malloc(sizeof(BTNode));
    if (new_node == NULL)
    {
        printf("malloc failed!\n");
        exit(-1);
    }
    new_node->data = x;
    new_node->left = new_node->right = NULL;
 
    return new_node;
}
 
//手动创建二叉树 
BTNode* CreateBinaryTree() 
{
    BTNode* nodeA = CreateNode('A');
    BTNode* nodeB = CreateNode('B');
    BTNode* nodeC = CreateNode('C');
    BTNode* nodeD = CreateNode('D');
    BTNode* nodeE = CreateNode('E');
    BTNode* nodeF = CreateNode('F');
 
    nodeA->left = nodeB;         //           A
    nodeA->right = nodeC;        //       B        C
    nodeB->left = nodeD;         //    D        E    F
    nodeC->left = nodeE;       
    nodeC->right = nodeF;        
 
    return nodeA;
}
 
int main()
{
    BTNode* root = CreateBinaryTree();
}

3.二叉树深度优先遍历

学习二叉树结构,最简单的方式就是遍历所谓二叉树遍历,就是按照某种特定的规则,一次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。 访问节点所做的操作要看具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其他运算的基础。

二叉树遍历(Traversal):沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。 按照规则,二叉树的遍历有:前序 / 中序 / 后序 的递归或非递归遍历。除了前序、中序和后续遍历外,我们还可以对二叉树进行层序遍历

比如二叉树的中序遍历:


3.1二叉树前序遍历

前序遍历(Preorder Traversal):访问根节点的操作发生在遍历其右子树之前。即:首先访问根结点,然后遍历左子树,最后遍历右子树(根左右)。

代码实现前序遍历:

cpp 复制代码
//二叉树前序遍历
void PreOrder(BTNode* root)
{
    //首先判断根是否为空,为空就返回
    if (root == NULL)
    {
        printf("NULL ");    // 暂时打印出来,便于观察       
        return;
    }
    //走到这里说明不为空,根据前序遍历,先访问根节点
    printf("%c ", root->data);
 
    //然后遍历左子树(利用递归)
    PreOrder(root->left);
 
    //最后遍历右子树(利用递归)
    PreOrder(root->right);
 
    //           A
    //       B        C
    //    D        E    F        前序:根 左 右
    //执行输出:  A B D NULL NULL NULL C E NULL NULL F NULL NULL
}

① 首先判断根是否为空,如果根为空,则返回。这里为了表示,我们把空节点以 " Ø " 打印出来。

② 如果跟不为空,这说明有数据。由于是前序遍历(Preorder),前序遍历是先访问根节点,然后遍历左子树,最后再遍历右子树。所以,我们这里先要访问的是根节点,我们把根节点的数据打印出来。

③ 然后我们需要遍历左子树,这时我们利用递归就可以实现。将根节点 root 的左数 left 传入 PreOrder 函数(将其左树看作根),一直递归下去,直到碰到 root == NULL 则返回。

④ 最后,遍历完左子树后遍历右子树。利用递归,方法同上。

3.2二叉树中序遍历

递归的中序和后序和前序差不多 顺序换一下就行

cpp 复制代码
//二叉树中序遍历
void InOrder(BTNode* root) 
{
    if (root == NULL) 
    {
        printf("NULL ");  
        return;
    }
 
    InOrder(root->left);
 
    printf("%c ", root->data);
 
    InOrder(root->right);
    //           A
    //       B        C
    //    D        E    F         中序:左  根  右
    //执行输出:NULL D NULL B NULL A NULL E NULL C NULL F NULL
}

3.3二叉树后序遍历

cpp 复制代码
void PostOrder(BTNode* root) 
{
    if (root == NULL) 
    {
        printf("NULL ");
        return;
    }
 
    PostOrder(root->left);
 
    PostOrder(root->right);
 
    printf("%c ", root->data);
    //           A
    //       B        C
    //    D        E    F        后序:左  右  根
    //执行输出:NULL NULL D NULL B NULL NULL E NULL NULL F C A
}

4.二叉树的几个接口函数

下面我们实现几个接口函数

cpp 复制代码
// 二叉树结点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);

4.1 求二叉树结点个数

和上面遍历一样,采用分治的思想:是空就返回0,不是空就返回左子树的结点和右子树的结点+1(本身)。

cpp 复制代码
int TreeSize(BTNode* root) 
{
    return root == NULL ? 0 
              : TreeSize(root->left) + TreeSize(root->right) + 1;
}

4.2 求二叉树叶子结点个数

同理,是空返回1,是叶子返回0,不是空也不是叶子的话就求其左子树的叶子+右子树的叶子

cpp 复制代码
int TreeLeafSize(BTNode* root) 
{
    if (root == NULL) 
    {
        return 0;
    }
 
    if (root->left == NULL && root->right == NULL) 
    {
        return 1;
    }
 
    return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

4.3求二叉树第k层结点个数

求当前树的第k层结点个数=其左子树第k-1层结点个数+其右子树第k-1层结点个数,k=1时返回1

比如上图K=3,求A的第3层结点个数就是求B的第2层的节点+C的第2层的结点。

cpp 复制代码
int TreeKLevelSize(BTNode* root, int  k) 
{
    assert(k > 0);
    if (root == NULL) //空结点返回0
    {
        return 0;
    }
    if (k == 1)
    {
        return 1;
    }
    return TreeKLevelSize(root->left, k - 1) 
        +  TreeKLevelSize(root->right, k - 1);
}

4.4查找树里面值为x的那个结点

思路:空就返回空,找到了就返回这个结点,没找到就到左子树和右子树去找,都没找到就返回空

cpp 复制代码
BTNode* TreeFind(BTNode* root, BTDataType x) 
{
    if (root == NULL)
    {
        return NULL;
    }
    if (root->data == x) 
    {
        return root;
    }
 
    BTNode* lret = TreeFind(root->left, x);
    if (lret) 
    {
        return lret;
    }
 
    BTNode* rret = TreeFind(root->right, x);
    if (rret) 
    {
        return rret;
    }
 
    return NULL;
}

比如要找E的位置:(找不到前都返回空)


4.5 二叉树销毁

这里采用后序遍历的销毁方式,因为用前序和中序要保存结点。

cpp 复制代码
// 一般,如果选择一级指针,存在野指针问题,调用BinaryTreeDestory的人,把指针置空
// 这样保持接口的一致性
void BinaryTreeDestory(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }
    BinaryTreeDestory(root->left);
    BinaryTreeDestory(root->right);
 
    //printf("%p %c\n", root, root->data);
    free(root);
    root = NULL;
}

前面函数的代码和测试:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
 
typedef char BTDataType;
 
typedef struct BinaryTreeNode 
{
    struct BinaryTreeNode* left;       // 记录左节点
    struct BinaryTreeNode* right;      // 记录右节点
    BTDataType data;                   // 存储数据
} BTNode;
 
//创建新节点
BTNode* CreateNode(BTDataType x) 
{
    BTNode* new_node = (BTNode*)malloc(sizeof(BTNode));
    if (new_node == NULL)
    {
        printf("malloc failed!\n");
        exit(-1);
    }
    new_node->data = x;
    new_node->left = new_node->right = NULL;
 
    return new_node;
}
 
//手动创建二叉树 
BTNode* CreateBinaryTree()
{
    BTNode* nodeA = CreateNode('A');
    BTNode* nodeB = CreateNode('B');
    BTNode* nodeC = CreateNode('C');
    BTNode* nodeD = CreateNode('D');
    BTNode* nodeE = CreateNode('E');
    BTNode* nodeF = CreateNode('F');
 
    nodeA->left = nodeB;         //           A
    nodeA->right = nodeC;        //       B        C
    nodeB->left = nodeD;         //    D        E    F
    nodeC->left = nodeE;
    nodeC->right = nodeF;
 
    return nodeA;
}
 
//二叉树前序遍历
void PreOrder(BTNode* root)
{
    //首先判断根是否为空,为空就返回
    if (root == NULL)
    {
        printf("NULL ");    // 暂时打印出来,便于观察       
        return;
    }
    //走到这里说明不为空,根据前序遍历,先访问根节点
    printf("%c ", root->data);
 
    //然后遍历左子树(利用递归)
    PreOrder(root->left);
 
    //最后遍历右子树(利用递归)
    PreOrder(root->right);
 
    //           A
    //       B        C
    //    D        E    F        前序: 根 左 右
    //执行输出:  A B D NULL NULL NULL C E NULL NULL F NULL NULL
}
 
//二叉树中序遍历
void InOrder(BTNode* root) 
{
    if (root == NULL) 
    {
        printf("NULL ");  
        return;
    }
 
    InOrder(root->left);
 
    printf("%c ", root->data);
 
    InOrder(root->right);
     //           A
     //       B        C
     //    D        E    F        中序:左 根 右
     //执行输出:NULL D NULL B NULL A NULL E NULL C NULL F NULL
}
 
//二叉树后序遍历
void PostOrder(BTNode* root) 
{
    if (root == NULL) 
    {
        printf("NULL ");
        return;
    }
 
    PostOrder(root->left);
 
    PostOrder(root->right);
 
    printf("%c ", root->data);
    //           A
    //       B        C
    //    D        E    F        后序:左  右  根
    //执行输出:NULL NULL D NULL B NULL NULL E NULL NULL F C A
}
 
int TreeSize(BTNode* root) 
{
    return root == NULL ? 0 
        : TreeSize(root->left) + TreeSize(root->right) + 1;
}
 
int TreeLeafSize(BTNode* root) 
{
    if (root == NULL) 
    {
        return 0;
    }
 
    if (root->left == NULL && root->right == NULL) 
    {
        return 1;
    }
 
    return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
 
int TreeKLevelSize(BTNode* root, int  k) 
{
    assert(k > 0);
    if (root == NULL) 
    {
        return 0;
    }
    if (k == 1)
    {
        return 1;
    }
    return TreeKLevelSize(root->left, k - 1) 
        +  TreeKLevelSize(root->right, k - 1);
}
 
// 查找树里面值为x的那个节点
BTNode* TreeFind(BTNode* root, BTDataType x) 
{
    if (root == NULL)
    {
        return NULL;
    }
    if (root->data == x) 
    {
        return root;
    }
 
    BTNode* lret = TreeFind(root->left, x);
    if (lret) 
    {
        return lret;
    }
 
    BTNode* rret = TreeFind(root->right, x);
    if (rret) 
    {
        return rret;
    }
 
    return NULL;
}
 
// 一般,如果选择一级指针,存在野指针问题,调用BinaryTreeDestory的人,把指针置空
// 这样保持接口的一致性
void BinaryTreeDestory(BTNode* root)
{
    if (root == NULL)
    {
        return;
    }
    BinaryTreeDestory(root->left);
    BinaryTreeDestory(root->right);
 
    //printf("%p %c\n", root, root->data);
    free(root);
    root = NULL;
}
 
int main()
{
    BTNode* root = CreateBinaryTree();
    PreOrder(root);
    printf("\n");
 
    InOrder(root);
    printf("\n");
 
    PostOrder(root);
    printf("\n");
 
    printf("TreeSize:%d\n", TreeSize(root));
 
    printf("TreeLeafSize:%d\n", TreeLeafSize(root));
 
    printf("TreeKLevelSize:%d\n", TreeKLevelSize(root, 3));
 
    printf("TreeFind:%p\n", TreeFind(root, 'E'));
    printf("TreeFind:%p\n", TreeFind(root, 'F'));
    printf("TreeFind:%p\n", TreeFind(root, 'X'));//打印0 :找不到
 
    BinaryTreeDestory(root);
    root = NULL;
    return 0;
}

**5.**二叉树广度优先遍历

5.1层序遍历

层序遍历(Level Traversal):设二叉树的根节点所在的层数为1的情况下,

从二叉树的根节点出发,首先访问第1层的树根节点,然后再从左到右访问第2层上的节点。

接着是第3层的节点......以此类推,自上而下、从左向右地逐层访问树的节点。
该如何实现层序遍历呢? 可以利用队列的性质来实现。

之前再讲过队列,这里你可以选择自己实现一个队列。

如果不想实现就直接复制即可,因为这里重点要学的是层序遍历。

这里就先不实现了,下一篇写写二叉树OJ,下下篇再实现(最下面已附链接)


本篇完。

下一篇写写二叉树的OJ题

相关推荐
代码游侠3 小时前
应用——文件I/O操作代码
linux·运维·c语言·笔记·学习·算法
发疯幼稚鬼3 小时前
d-堆,左式堆及斜堆的简单介绍
算法
不会代码的小猴3 小时前
C++的第十三天笔记
c++·笔记·算法
UP_Continue3 小时前
哈希表实现--开放定址法
数据结构·哈希算法·散列表
brave and determined3 小时前
传感器学习(day04):红外感知:从经典热释电开关到智能时代的隐形慧眼
嵌入式硬件·算法·传感器·红外·嵌入式设计·红外矩阵·人体红外
南极星10053 小时前
OPENCV(python)--初学之路(十六)SURF简介
python·opencv·算法
lxh01133 小时前
合并区间题解
数据结构·算法·leetcode
yongui478343 小时前
基于MATLAB的轴承表面织构油膜参数计算程序
数据结构·算法·matlab
猎板PCB黄浩3 小时前
多层电路板技术深度解析:高密度集成时代的核心支撑
网络·人工智能·算法