C语言二叉树

前言

        随着栈和队列的学习完成,接下来就到了二叉树环节。二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。

        到在此之前,先介绍一下,什么是树的结构。

一. 树

1. 树的定义

        在数据结构中,有一种被称为树的结构。和链式结构相似,需要用一个节点中的指针去查找下一块位置。与链表不同的是,指向其他节点的指针会大于一。也就是如图1-1所示:

图1-1 树

        根据上图我们能够很清楚的了解数的定义:每个节点都会存储数据和指针,树有一个特点,就是虽然指针很多,但是能找到一个固定节点的指针只有一个。

        为了方便我们更加清楚地描述树,接下来将会讲解相关概念。

        (1)节点的度:一个节点含有的子树个数。(例如节点1的度为6,节点2的度为2)

        (2)叶子节点或终端节点:度为0的节点被称为叶节点。(例如图1-1中6、7等)

        (3)非叶子节点或非终端节点:度不为0的节点。(例如1、2等)

        (4)双亲节点或父节点:有子节点的节点,非叶子节点都是某个节点的父节点。(例如1、2等)

        (5)孩子节点或子节点:一个节点有父节点则为该父节点的子节点。(如图除了1节点之外都能够是子节点)

        (6)兄弟节点:具有相同父节点的节点。(例如2、3、4、5都是兄弟节点)

        (7)树的度:整个树中最大的度。(指1节点的度)

        (8)节点的层次:开始为1层,向下增加。(例如1节点的层数为1,10节点的层数是3)

        (9)树的高度或深度:最大节点的层次,也就是最大层。(这里一共有3层,所以树的高度为3)

        (10)堂兄弟节点:父节点在同一层的节点,但是父节点不相同。(例如7和10是堂兄弟节点)

        (11)祖先节点:顾名思义,特定节点向上的所有节点都是祖先节点。(例如10的祖先节点有1和5)

        (12)子孙节点:顾名思义,向下的所有子孙。(例如1节点,剩下的节点都是子孙节点)

        这之中需要重点掌握的是(2)(4)(5)。

2. 树的结构

        有图1-1可知,树的子节点都是不固定的,那么我们没办法直接构建相同结构体来表示数。因此有人想出了另一种方法来接收描述树。用一个子节点来找到他的兄第节点,如图1-2所示:

图1-2 树2

        红色部分为实际数据存储方式,黑色为树原本结构。如此解决了结构体多定义的问题,形成了一种只有子节点指针和右兄弟指针的方式。代码如下:

struct TreeNode
{
    int val; //存储的数据
    struct TreeNode* leftchild; // 左孩子指针
    struct TreeNode* rightbrother; // 右兄弟指针
};

二. 二叉树

1. 二叉树定义

        二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。

图2-1 二叉树

        如图2-1所示,二叉树的子节点数最大为2.

        特殊的二叉树:

        (1)满二叉树:每一层都是满的。(如图2-2所示)

        (2)完全二叉树:除了最后一层之外都是满的,并且最后一层的元素是连续的。(如图2-3,满二叉树是特殊的完全二叉树)

图2-2 满二叉树                                             图2-3 完全二叉树

2. 二叉树的结构

        二叉树的结构有两种,一种是物理结构使用顺序表存储数据,另一种是按照逻辑结构一样用左右指针储存数据。对应的分别为图2-4中的物理结构和逻辑结构。

图2-4 树的储存结构

三. 二叉树的实现

1. 顺序表形式的二叉树

        和顺序表一样,我们需要增删查改等功能,只是和顺序表不同我呢吧需要排序。因为需要排序,所以我们需要准确定义父子节点的位置关系。以图2-4为例,F的左孩子为C,右孩子为E。F在顺序表中的位置是0,C和E的分别为1和2。他们的实际关系相当于:

        左孩子 = 父节点 * 2 + 1。      右孩子 = 父节点 * 2 + 2

        这个公式对于二叉树中任意节点都生效。

        如果需要建立小堆(及父节点的数小于子节点),那么数据尾插之后需要向上遍历节点

图3-1 二叉树插入1

        如图3-1的二叉树中,需要插入节点的值为8,那么首先我们需要把8尾插进入顺序表中,如图3-2所示。

图3-2 二叉树插入2

        然后我们向上遍历节点,比如这里的8需要和16比较,小于16就需要交换他们的位置,交换完成之后和9比较,小于9,继续交换。直到遇到6,大于6就不在交换,停止遍历。

        8刚插入的时候位置为 7,父节点的位置根据公式计算 (7 - 1)/ 2。为 3。之后(3 - 1)/ 2 = 1。就能正确找到16和9的位置。剩下的就是构建函数交换数据即可。结果为图3-3所示。

图3-3 二叉树插入3

        介绍完插入之后就是删除,读取头节点了,从图3-3中不难看出删除头节点6之后,剩下的数据不好删除,那么我们就需要修改一下删除的方法。首先将首元素和末尾元素交换,然后删除最后的元素,最后将首元素位置的数据重新排序。重新排序的方法(小堆),找子节点中较小的那位,如果,数据比这个值大,就交换数据并继续向下遍历节点,反之就结束遍历。具体方法如图3-4 所示:

图3-4 二叉树删除

        删除中找到较小节点的方法使用贪心的方式,假设左孩子更小,然后与右孩子比较,大于右孩子就将更小的孩子改为右孩子,如果没有右孩子,就与左孩子比较。都没有就结束遍历,删除完成。

2. 顺序表二叉树代码

        代码部分的讲解可以从1. 顺序表形式的二叉树 中找到,或者与顺序表内容相似,故不在讲解。

2.1. 基础代码

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>

#define HPINIT 20

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a; // 指向存储数据的空间
	int _size; // 数据个数
	int _capacity;  // 容量
}Heap;

// 堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);
// 堆的删除
void HeapPop(Heap* php);
// 取堆顶的数据
HPDataType HeapTop(Heap* php);
// 堆的数据个数
int HeapSize(Heap* php);
// 堆的判空
bool HeapEmpty(Heap* php);

// 父大于子判真(小堆)
bool PCCompare_s(HPDataType parent, HPDataType child);
// 交换数据
void Swap(HPDataType* p1, HPDataType* p2);
// 向上调整
void AdjustUp(HPDataType* _a, int child);
// 向下调整
void AdjustDown(HPDataType* _a, int size, int parent);

2.2. 堆的初始化

// 堆的初始化
void HeapInit(Heap* php)
{
    assert(php);

    // 申请初始空间 HPINIT == 4
    php->_a = (HPDataType*)malloc(sizeof(HPDataType) * HPINIT);
    if(php->_a == NULL)
    {
        perror("HeapInit malloc fail");
        exit(1);
    }

    // 容量和个数初始化
    php->_size = 0;
    php->_capacity = HPINIT;
}

// 堆的销毁
void HeapDestory(Heap* php)
{
    assert(php);

    // 释放内存
    free(php->_a);
    // 内容全部置为0或者NULL
    php->_a = NULL;
    php->_size = php->_capacity = 0;
}

2.3. 父大于子判真(小堆)

// 父大于子判真(小堆)
bool PCCompare_s(HPDataType parent, HPDataType child)
{
    return parent > child;
}

2.4. 交换数据

// 交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{
    HPDataType tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

2.5. 向上调整

// 向上调整
void AdjustUp(HPDataType* _a, int child)
{
    assert(_a);

    // 父节点的位置等于(child - 1) / 2;
    int parent = (child - 1) / 2;

    // 反复向上调整直到子节点到树顶
    while(child > 0)
    {
        if(PCCompare_s(_a[parent], _a[child]))// 满足交换条件就调整父子节点数据位置
        {
            Swap(&_a[parent], &_a[child]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else// 反之就跳出循环(无需调整)
        {
            break;
        }
    }
}

2.6. 向下调整

// 向下调整
void AdjustDown(HPDataType* _a, int size, int parent)
{
    assert(_a);
    // 假设左孩子更小, 公式:child = parent * 2 + 1;
    int child = parent * 2 + 1;

    // 孩子节点没越界就继续比较
    while(child < size)
    {
        // 调整左右孩子,取更小的
        if(child + 1 < size && PCCompare_s(_a[child], _a[child + 1]))
        {
            ++child;
        }

        // 如果满足条件就交换父子位置并继续向下遍历
        if(PCCompare_s(_a[parent], _a[child]))
        {
            Swap(&_a[parent], &_a[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else// 反之,跳出循环(无需继续调整)
        {
            break;
        }
    }
}

2.7. 堆的插入

// 堆的插入
void HeapPush(Heap* php, HPDataType x)
{
    assert(php);

    // 容量不足扩容
    if(php->_capacity == php->_size)
    {
        // int tmp = php->_capacity * 2;
        HPDataType* newnode = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * php->_capacity * 2);
        if(newnode == NULL)
        {
            perror("HeapPush malloc fail");
            exit(1);
        }

        php->_a = newnode;
        php->_capacity *= 2;
    }

    // 插入数据
    php->_a[php->_size++] = x;

    // 堆尾向上调整
    AdjustUp(php->_a, php->_size - 1);
}

2.8. 堆的删除

// 堆的删除
void HeapPop(Heap* php)
{
    assert(php);
    assert(!HeapEmpty(php));

    // 删除堆顶数据
    Swap(&php->_a[0], &php->_a[php->_size - 1]);
    --php->_size;

    // 堆顶数据向下调整
    AdjustDown(php->_a, php->_size, 0);
}

2.9. 取堆顶的数据

// 取堆顶的数据
HPDataType HeapTop(Heap* php)
{
    assert(php);
    assert(!HeapEmpty(php));

    return php->_a[0];
}

2.10. 堆的数据个数

// 堆的数据个数
int HeapSize(Heap* php)
{
    assert(php);

    return php->_size;
}

2.11. 堆的判空

// 堆的判空
bool HeapEmpty(Heap* php)
{
    assert(php);

    return php->_size == 0;
}

3. 测试二叉树(堆)

        和顺序表一样,测试插入,排序和删除。

void TestHeap()
{
    Heap hp;
    HeapInit(&hp);
    HPDataType arr[] = {9,88,666,28,114,254,612,450,153,24};
    int size_a = sizeof(arr) / sizeof(arr[0]);

    for(int i = 0; i < size_a; ++i)
    {
        HeapPush(&hp, arr[i]);
    }

    printf("堆的大小为:%d\n", HeapSize(&hp));

    while(!HeapEmpty(&hp))
    {
        printf("%d ", HeapTop(&hp));
        HeapPop(&hp);
    }
    printf("\n");

    HeapDestory(&hp);
}

int main()
{
    TestHeap();

    //TestHeapSort();
    return 0;
}

4. 堆排序

        了解了二叉树之后,我们想要直接堆一个数组进行排序呢?

        我们可以利用之前的调整函数堆数组进行调整,有两种方式:

        (1)从上向下遍历数组,每个节点都执行一次向上遍历。时间复杂度O(N^2)

        (2)从有子节点的最后一位开始,从下向上遍历数组,每个节点执行一次向下遍历。时间复杂度O(N)

        这里我们选择方法(2),那么得到小堆之后,数组不一定是有序的,这个时候我们仍然需要排序,但是我们只知道,堆中的头节点是最小的。所以就将头结点位置数据和没有交换过的尾数据相交换,并从头节点向下遍历一次堆。最后数组变成了递增的数组,

        特点是递增建立大堆,递减建立小堆。

        那么代码如下:

// 对数组进行堆排序
void HeapSort(int* a, int n)
{
    assert(a);

    //向下调整建堆(小堆)
    int i = n / 2 - 1;
    while(i >= 0)
    {
        AdjustDown(a, n, i);
        --i;
    }

    // 得到降序数组
    while(--n)
    {
        Swap(&a[0], &a[n]);
        AdjustDown(a, n, 0);
    }
}

        测试代码:

void TestHeapSort()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};

    int sz = sizeof(arr) / sizeof(arr[0]);
    HeapSort(arr, sz);

    for(int i = 0; i < sz; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

5. 传统二叉树实现

5.1. 二叉树的传统结构

        传统二叉树,图2-4中和逻辑结构相同的物理结构。现阶段刚接触到二叉树,真正需要插入数据的话还要在搜索二叉树中才有意义。现阶段的代码实现中会缺少相关插入删除的函数,到C++之后接触到搜索二叉树、红黑树、B树的实收再讲。

        因为逻辑结构和物理结构相同,所以元素的访问有所不同,那么我们会需要新的函数,例如:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

        这些函数现阶段我们都可以实现了,而且,有许多共通的地方。在开始学习上面的函数之前,我们首先需要了解一下二叉树的三种基础的遍历结构。

5.2. 二叉树节点的遍历

        二叉树有3种基础的遍历方式,分为:前序遍历、中序遍历、后续遍历。

        三种遍历的不同之处在于根节点操作的顺序不同,例如:

        (1)前序遍历的顺序为:根节点、左子树、右子树。

        (2)中序遍历的顺序为:左子树、根节点、右子树。

        (3)后序遍历的顺序为:左子树、右子树、根节点。

        具体的遍历方式,我们用图片的方式说明。

        如图3-5所示,我们拥有一颗树。

图3-5 树

5.2.1. 前序遍历

        前序遍历就是先访问根节点然后访问左子树然后是右子树,那么它的访问结果应该是:F C A # # D # # E H # # G # #.。其中‘#’表示空节点,如果去掉空节点前序遍历为: F C A D E H G。辨识度就比较低了,而且拼不出来一个唯一的树。

        实现遍历的具体过程如图3-6所示

图3-6 前序遍历

5.2.2. 中序遍历

        讲过了前序遍历之后,中序遍历会好理解很多。可以理解为左子树访问完回到根,然后访问右子树。如图3-7所示,中序遍历的结果为:# A # C # D # F # H # E # G #,去掉空节点结果为:A C D F H E G。

图3-7 中序遍历

6.2.3. 后序遍历

        根据之前的经验,直接说后序遍历的结论吧。后续遍历: # # A # # D C # # H # # G E F。去掉空节点结果为: A D C H G E F。

图3-8 后序遍历

5.3. 遍历的规律

        根据之前三种遍历方式,我们其实可以从结果中找到遍历的线索。比如从结果中看到子树,具体描述如图3-9所示:

图3-9 从遍历顺序中找树

5.4. 层序遍历

        层序遍历就是按照每一层每一层总左往右按顺序访问,这个顺序其实和顺序表实现的二叉树顺序相同,假设现有一颗如图3-5所示的树。层序遍历的结果为: F C E A D H G,如果需要在代码中实现要怎么做呢?我们可以找一个队列存节点数据,先推送F进去,F取出来之后推送子节点C、E入队列,输出完一层的数据后重新确认下一层数据个数。

        依次向下,直到队列中没有数据。

6. 传统二叉树实现代码

6.1. 创建二叉树

        创建二叉树,我们需要先访问根节点,所以采用前序遍历的思路链接节点即可,代码如下:

// 申请节点
BTNode* BuyNode(BTDataType x)
{
    BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
    if(newnode == NULL)
    {
        perror("BuyNode malloc fail");
        exit(1);
    }

    newnode->_data = x; // 将数据置入
    newnode->_left = newnode->_right = NULL; //初始化指针
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
    if(a[*pi] == '#')
    {
        ++*pi;
        return NULL;
    }

    BTNode* node = BuyNode(a[*pi]);
    ++*pi;
    node->_left = BinaryTreeCreate(a, n, pi);
    node->_right = BinaryTreeCreate(a, n, pi);

    return node;
}

6.2. 销毁二叉树

        销毁二叉树,根节点我们需要留到最后删除,先删除左右子树的数据,代码如下:

// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
    assert(root);

    if(*root == NULL)
    {
        return;
    }

    //后序遍历
    BinaryTreeDestory(&((*root)->_left)); 
    BinaryTreeDestory(&((*root)->_right));
    free(*root); // 删除根节点空间并置空
    *root == NULL;
}

6.3. 统计二叉树节点个数

        拆分成子问题,将树的节点个数分为,左子树所有节点个数+右子树所有节点个数 + 根节点,代码如下:

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
    if(root == NULL) // 节点为空返回0
    {
        return 0;
    }

    return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1; // 左子树加右子树节点加自己
}

6.4. 二叉树叶子节点的个数

        思路和6.3. 相同,找左右子树根节点个数,总和即可。

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
    if(root == NULL) // 节点为空返回0
    {
        return 0;
    }
    if(root->_left == NULL && root->_right == NULL) // 左右都为空则是子节点,返回1
    {
        return 1;
    }

    return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right); //至少有一个不为空,总数为左子树子节点+右子树子节点
}

6.5. 二叉树第k层节点个数

        大致思路不变,找一个数字来记录是否到了k层即可。

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    if(root == NULL) // 节点为空返回0
    {
        return 0;
    }
    if(k == 1) // 是第k层节点,返回1
    {
        return 1;
    }

    return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1); // 层数减一,找左右子树中k层节点的总和
}

6.6. 查找二叉树中值为x的节点

        通过前序遍历将所有节点都查找一遍,最后返回找到的节点即可。

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if(root == NULL)// 空节点直接返回空
    {
        return NULL;
    }

    if(root->_data == x) // 找到值了,返回该节点
    {
        return root;
    }

    BTNode* leftnode = BinaryTreeFind(root->_left, x); // 找左树中是否有k
    BTNode* rightnode = BinaryTreeFind(root->_right, x); // 找右树中是否有k

    if(leftnode) // 左不为空返回左,反之返回右
    {
        return leftnode;
    }
    else
    {
        return rightnode;
    }
}

6.7. 前序遍历

        之前有说过什么事前序遍历,具体递归情况请读者自行分析,或者我下次单独出一期讲。中序和后序遍历也一样,直接给代码了。

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
    if(root == NULL)
    {
        printf("#");
        return;
    }

    printf("%c", root->_data);
    BinaryTreePrevOrder(root->_left);
    BinaryTreePrevOrder(root->_right);
}

6.8. 中序遍历

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
    if(root == NULL)
    {
        printf("#");
        return;
    }

    BinaryTreeInOrder(root->_left);
    printf("%c", root->_data);
    BinaryTreeInOrder(root->_right);
}

6.9. 后序遍历

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
    if(root == NULL)
    {
        printf("#");
        return;
    }

    BinaryTreePostOrder(root->_left);
    BinaryTreePostOrder(root->_right);
    printf("%c", root->_data);
}

6.10. 层序遍历

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
    assert(root);//判空

    Queue q;//建立队列
    QueueInit(&q);// 初始化队列
    QueuePush(&q, root);// 首先推送第一层的根节点
    int levelSize = QueueSize(&q); // 确认每一层元素个数

    while(!QueueEmpty(&q)) //队列非空就继续
    {
        while(levelSize--) // 分别打印每一层
        {
            BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
            QueuePop(&q);

            printf("%c ", front->_data); // 打印

            if(front->_left) // 存入下一行存在的节点
            {
                QueuePush(&q, front->_left);
            }
            if(front->_right)
            {
                QueuePush(&q, front->_right);
            }
        }

        levelSize = QueueSize(&q);// 为下一层做准备
    }
}

6.11. 判断二叉树是否是完全二叉树

        这里直接使用层序遍历的思路,但是空指针也存到队列之中,如果遇到节点为空就结束层序遍历,查看队列中剩余元素的内容是否都为空,如果都为空则该树是完全二叉树,反之就不是。代码如下:

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
    assert(root);
    // 使用和队列相同的思路,但是存入空节点,如果空节点之后仍然存在着节点就不是完全二叉树

    Queue q;//建立队列
    QueueInit(&q);// 初始化队列
    QueuePush(&q, root);// 首先推送第一层的根节点
    int levelSize = QueueSize(&q); // 确认每一层元素个数

    while(!QueueEmpty(&q)) //队列非空就继续
    {
        while(levelSize--) // 分别打印每一层
        {
            BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
            QueuePop(&q);

            if(front == NULL) // 如果节点为空就跳出循环
            {
                goto end;
            }

            // 空节点也存储
            QueuePush(&q, front->_left);
            QueuePush(&q, front->_right);
        }

        levelSize = QueueSize(&q);// 为下一层做准备
    }

end:
    while(!QueueEmpty(&q))
    {
        BTNode* front = QueueFront(&q); // 按照顺序取出每一行的元素
        QueuePop(&q);

        if(front) // 如果存在节点不为空,就不是完全二叉树
        {
            return  false;
        }
    }

    return true; // 剩余节点均为空,说明是完全二叉树
}

7. 测试传统二叉树

        根据以下代码,按照顺序,确认每个函数是否有问题。

void TestBTtree()
{
    BTNode* bt;
    BTDataType ch[] = "ABD##E#H##CF##G##";
    int size = strlen(ch);
    int i = 0;
    bt = BinaryTreeCreate(ch, size, &i);

    printf("二叉树节点个数:%d\n", BinaryTreeSize(bt));
    printf("二叉树叶子节点个数:%d\n", BinaryTreeLeafSize(bt));
    printf("二叉树第%d层节点个数:%d\n", 3, BinaryTreeLevelKSize(bt, 3));

    BTNode* search = BinaryTreeFind(bt, 'G');
    if(search)
    {
        printf("找到了,节点为%p->%c\n", search, search->_data);
    }
    else
    {
        printf("二叉树中不存在该节点\n");
    }

    search = BinaryTreeFind(bt, 'N');
    if(search)
    {
        printf("找到了,节点为%p->%c\n", search, search->_data);
    }
    else
    {
        printf("二叉树中不存在该节点\n");
    }

    BinaryTreePrevOrder(bt); // 前序遍历
    printf("\n");
    BinaryTreeInOrder(bt);  // 中序遍历
    printf("\n");
    BinaryTreePostOrder(bt);    // 后续遍历
    printf("\n");

    BinaryTreeLevelOrder(bt);//层序遍历
    printf("\n");

    if(BinaryTreeComplete(bt))
    {
        printf("该树是完全二叉树\n");
    }
    else
    {
        printf("该树不是完全二叉树\n");
    }

    BinaryTreeDestory(&bt);
}

int main()
{
    TestBTtree();
    return 0;
}

        运行结果如下:

        树的逻辑样貌如下图所示:

图3-10 

        如图3-10所示,检测上述代码运行结果后对比无误。

作者结语

        二叉树整体内容还挺多的,有不是完全二叉树的,也有只有逻辑上是二叉树的。学习完本节之后其实也只能算是开端,离实际应用还相差很远。突然觉得当程序员是真的太难了,我需要鼓励朋友们。

        有的地方没有细讲,有点偷懒,毕竟写博客没什么明面上的奖励,所以写起来没什么干劲,作为人的局限性一下就突出出来了,没有好处就不想干活。

        好在写完了,勉强能看,细化是真不想细化了。

  • 18
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值