链式二叉树的实现

本文详细讲解了树的概念、二叉树的结构,重点介绍了三种二叉树遍历方法(前序、中序、后序)的递归实现,剖析了Treesize函数的经典错误及两种解决方案,深入探讨了函数递归的原理和注意事项。此外,还涵盖了叶子结点个数、树的高度、第k层节点数计算以及判断完全二叉树的方法。
摘要由CSDN通过智能技术生成

目录

树概念及结构

二叉树遍历

三种遍历代码的实现                                                          ​编辑

Treesize函数的经典错误以及实现方法

第一种方法:

   第二种方法:

函数递归的独特理解

叶子结点的个数函数

求高度

第k层的节点个数

层序遍历

判断完全二叉树

其他的函数代码实现


树概念及结构

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因
为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此,树是递归定义的。

 

当然 ,二叉树也是递归定义的

任何一个二叉树都要拆成三个部分

1 根

2左子树

3右子数

二叉树遍历

二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。 根 左子树 右子树

2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。

左子树 根 右子树

3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

左子树 右子树 根

递归实际上是把大问题拆成小问题 结束条件是NULL

  其中N代表NULL

链式二叉树

三种遍历代码的实现                                                          

当前函数结束回到他调用的地方 也就是回到使用他的函数的语句,然后由于程序的顺序结构继续往下走

时间是一去不复返的 而空间可以重复利用。

函数调用和返回过程中,系统会自动管理函数栈帧的创建和销毁,我们不需要手动操作。当一个函数返回时,它对应的栈帧就会被弹出,控制权会返回到调用它的上一层函数。

Treesize函数的经典错误以及实现方法

经典错误 :每次递归调用都会建立新的栈帧 每次的size都是新的size。

int TreeSzie(TreeNode* root)

{

Int size=0;

If(root==NULL)

{

Return;

}

   Size++

TreeSize(root->left);

TreeSize(root->right);



}

非这里是搞一个静态的size,但是局部的静态化变量只会初始化一次(在局部函数中)

局部的静态变量虽然生命周期是在全局,但是作用域只在当前的函数。

实现方法:

第一种方法:

size当成全局变量放在外面即可 每次用size函数之前先把size初始化置零一次 然后遍历链式二叉树 不为空 size++

   第二种方法:

逐层递归  类似与校长统计全校人数 校长找院长 院长找导员 导员找班长 班长找宿舍长 宿舍长数 然后返回数字 加上自身 后面逐层上报。依次往下找 找到可统计的最小单位,然后在加上自身逐层上报。

 

函数递归的独特理解

从前面两个函数的实现,我们不难看出递归是基于函数栈帧的开辟与销毁的,所谓递归,就是先“递”后“归”,递中有递,归中有归,可以想象的理解成套娃。函数递归与函数的顺序结构可谓是相辅相成,二位一体的,函数递归就是要把大问题分解成小问题去解决,且大问题与分解成的基本小问题的解决逻辑是统一的!把大问题分解到一个不能在分解的具有基本性质的小问题,而这个小问题具有一个特定的“返回值,返回条件”,递的过程实际上是大问题分解成诸多基本问题的过程,而归则是带有具体“答案”的小问题不断汇聚合体成大问题的过程。

一去一回,一递一归,先分解后汇合,两个互逆的过程的组合在整体的运行逻辑还是一致的!

一来一回却解决了许多大问题,递归思想的核心就在这里了!

当前函数结束回到他调用的地方 也就是回到使用他的函数的语句,然后由于程序的顺序结构继续往下走时间是一去不复返的 而空间可以重复利用。

函数调用和返回过程中,系统会自动管理函数栈帧的创建和销毁,我们不需要手动操作。当一个函数返回时,它对应的栈帧就会被弹出,控制权会返回到调用它的上一层函数。

因此基于其基本性质,我们发现函数递归时,需要注意两个大问题

1返回条件(取下娃娃壳子的条件)

2分治子问题(一个大娃娃能分成几个基本的小娃娃)

做好函数递归就需要多画递归展开图,理解函数栈帧的创建与销毁。

以上思路,我们来完成一个函数的递归

叶子结点的个数函数

分析可得:

子问题分治:左子树的叶子结点个数+右子树的叶子结点个数

返回条件:

1、空树 返回0

2、叶子返回1

实现:

求高度

不要写三目 因为只判断大小 不记录 会有重复计算(三目没理解)

fmax函数

分治子问题:

1 返回0

2不是空 左子树和·右子树 大的那个加一

第k层的节点个数

分治子问题:

1空 返回0

2 不为空 且k==1,返回1

3 不为空 且k>1  返回左子树的k-1层+右子树的k-1层

层序遍历

Pop的是QueueNode 因此pop掉QueueNode,不会使front变成野指针,因为front是TreeNode*形的。 因为QueueNode的val是Treenode* val存的是Treenode的地址,通过val的解引用去访问原来二叉树中具体的值,这里巧妙的对队列的应用变形,以前队列存的是单纯的值,现在存的是地址,通过地址这个中介去间接的访问原二叉树中的值

每一层打印出来 打印一层换行,

LevelSize(用队列的size函数就可以,因为队列出一个结点就会进两个子节点,队列的大小就是这层的子节点个数)  每一层的数据个数,控制一层一层出。通过while循环来走

While(LevelSize--)

判断完全二叉树

用层序遍历,遇到空以后不能有非空,所有元素必须是连续的。

如果遇见空后面的全是空就是完全二叉树。

如果遇见空后面的有非空就不是完全二叉树

遇到空了,非空元素一定进入队列了。

其他的函数代码实现

#include"BinaryTree.h"


TreeNode* BuyTreeNode(int x)
{
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    assert(node);
    node->data = x;
    node->left = NULL;
    node->right=NULL;
    return node;
}

TreeNode* CreateTreeNode0()
{
    TreeNode* node1 = BuyTreeNode(1);
    TreeNode* node2 = BuyTreeNode(2);
    TreeNode* node3 = BuyTreeNode(3);
    TreeNode* node4 = BuyTreeNode(4);
    TreeNode* node5 = BuyTreeNode(5);
    TreeNode* node6 = BuyTreeNode(6);
    /*TreeNode* node7 = BuyTreeNode(7);
    //TreeNode* node8 = BuyTreeNode(8);*/
    node1->left = node2;
    node1->right = node4;
    node2->left = node3;
    node4->left = node5;
    node4->right = node6;
    return node1;
}
TreeNode* CreateTreeNode(char* a, int* pi)
{
    if (a[*pi] == '#')
    {
        (*pi)++;
        return NULL;
    }
    TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
    if (root == NULL)
    {
        perror("malloc fail");
        exit(-1);

    }
    root->data = a[(*pi++)];
    root->left = CreateTreeNode(a, pi);
    root->right = CreateTreeNode(a, pi);
    return root;

}
void TreeNodeprevorder(TreeNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
    printf("%d ", root->data);
     TreeNodeprevorder( root->left);
     TreeNodeprevorder( root->right);


}
void TreeNodeinorder(TreeNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
     TreeNodeinorder(root->left);
    printf("%d ", root->data);
     TreeNodeinorder(root->right);


}

void TreeNodepostorder(TreeNode* root)
{
    if (root == NULL)
    {
        printf("N ");
        return;
    }
    
     TreeNodepostorder(root->left);
     TreeNodepostorder(root->right);
    printf("%d ", root->data);

}
//int TreeNodeSize(TreeNode* root)
//{
//    static int size = 0; //这种写法的size在递归时是逐渐累积的,因为size是静态变量,因此在每一次使用前需要初始化size=0
//    if (root == NULL)
//    {
//        return 0;
//    }
//    size++;
//    TreeNodeSize(root->left);
//    TreeNodeSize(root->right);
//}
int TreeNodeSize(TreeNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    return TreeNodeSize(root->left) + TreeNodeSize(root->right)+1;
}
int TreeNodeLeafSize(TreeNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    if ((root->left == NULL) &&
        (root->right == NULL))
    {
        return 1;
    }
    return TreeNodeLeafSize(root->left) + TreeNodeLeafSize(root->right);
}


int TreeHeight(TreeNode* root)
{
    if (root == NULL)
    {
        return 0;
    }

    return fmax(TreeHeight(root->left), TreeHeight(root->right))+1;
}
int TreeLevelKSize(TreeNode* root, int k)
{
    assert(k > 0);
    if (k == 1)
    {
        return 1;
    }

    return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);

}


TreeNode* TreeNodefind(TreeNode* root, int x)
{
    if (root == NULL)
    {
        return NULL;
    }
    if (root->data == x)
    {
        return root;
    }

    TreeNode* ret1 = TreeNodefind(root->left, x);
    if (ret1)
        return ret1;
    TreeNode* ret2 = TreeNodefind(root->right, x);
    if (ret2)
        return ret2;
    return NULL;
}
void DestroyTree(TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }
    DestroyTree(root->left);
    DestroyTree(root->right);
    free(root);//外置root为空,要不然用二级指针。
}
void Levelorder(TreeNode* root)
{ 
    Queue q;
    QueueInit(&q);
    if (root)
        QueuePush(&q,root);
    int levelsize = 1;
    while (!QueueEmpty(&q))
    {
        while (levelsize--)
        {
            TreeNode* front = QueueFront(&q);
            QueuePop(&q);

            printf("%d ", front->data);
            if(front->left)
                QueuePush(&q,front->left);
            if (front->right)
                QueuePush(&q, front->right);

            
        }
        printf("\n");
        levelsize = QueueSize(&q);

    }
    printf("\n");

    QueueDestroy(&q);

}
bool TreeComplete(TreeNode* root)//遇到空之后就不能再遇见非空元素了,否则就不连续。就不是完全二叉树。
{
    Queue q;
    QueueInit(&q);
    if (root)
        QueuePush(&q, root);
    while (!QueueEmpty(&q))
    {
        
            TreeNode* front = QueueFront(&q);
            QueuePop(&q);
            if (front == NULL)
                break;
            
                QueuePush(&q, front->left);
                QueuePush(&q, front->right);
    }
        
    while (!QueueEmpty(&q))
    {

        TreeNode* front = QueueFront(&q);
        QueuePop(&q);
        if (front)
        {
            QueueDestroy(&q);
            return false;
        }
    }
    QueueDestroy(&q);
    return true;
}

#include"Queue.h"

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->phead = NULL;
    pq->tail = NULL;
    pq->size = 0;

}
void QueueDestroy(Queue* pq)
{
    assert(pq);

    QNode* cur = pq->phead;
    while (cur)
    {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    pq->phead = NULL;
    pq->tail = NULL;
    pq->size = 0;

}
void QueuePush(Queue* pq,QDatatype x)
{
    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        return;
    }
    newnode->val = x;
    newnode->next = NULL;

    if (pq->tail == NULL)
    {
        pq->tail = pq->phead = newnode;
    }
    else
    {
        pq->tail->next = newnode;
        pq->tail = newnode;
    
    }

    pq->size++;

}


void QueuePop(Queue* pq)
{
    assert(pq);
    assert(pq->phead);
    QNode* tmp = pq->phead;
    pq->phead = pq->phead->next;
    free(tmp);
    tmp = NULL;
    if (pq->phead == NULL)
        pq->tail = NULL;
    pq->size--;

}

QDatatype QueueFront(Queue* pq)
{
    assert(pq);
    assert(pq->phead);

    return pq->phead->val;

}

QDatatype QueueBack(Queue* pq)
{
    assert(pq);
    assert(pq->tail);

    return pq->tail->val;

}

bool QueueEmpty(Queue* pq)
{
    assert(pq);

    return pq->phead == NULL;
}

int QueueSize(Queue* pq)
{
    assert(pq);


    return pq->size;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值