【数据结构基础/经典OJ题目】二叉树第二弹之二叉树的链式结构实现、基础堆接口函数的实现、基础OJ、IO试题的解答(编写思路加逻辑分析加代码实操,一应俱全的汇总)

二叉树的链式结构及实现

普通二叉树的增删查改没有什么价值,因为用来存放数据太复杂了。它的价值在于

  • 在他的基础之上增加一些性质,才有意义,比如
    • 搜索二叉树,左子树的值都比父节点小,右子树的值逗比父节点大。最多可以查找高度次。->平衡搜索二叉树、AVLTree、红黑树 -> B树
    • huffman tree

不关注增删查改,关注递归遍历结构。

学习这个是为了后面学习更有用的树打基础,同时很多oj题目结构普通二叉树。

前序、中序以及后序遍历

学习二叉树的结构,最简单的方式就是遍历。二叉树遍历 (Traversal)时按照某种待定的规则,依次堆二叉树中的节点进行相应的操作,并且每个节点只操作一次。

二叉树可以分为根、左子树和右子树。走到空就中止。

二叉树的遍历分为:前序/中序/后序的递归结构遍历。在这里插入图片描述

前序遍历(preorder Traversal) – 访问跟节点的操作发生在遍历左右子树之前。
根 -> 左子树 -> 右子树
也就是说,遍历顺序为: A->B->D ->NULL(left) -> NULL(right) -> NULL(B下面) -> C -> E -> NULL(left) ->NULL(right)->F->NULL(left)->NULL(right)
前期学习应该把空写出来,表示清楚的遍历顺序,但是在打印的时候由于是空,所以不会打印出来。

中序遍历(Inorder Traversal) – 访问根节点的操作发生在遍历其左右子树中间。
左子树 -> 根 -> 右子树
也就是访问A先访问A的左子树B,访问B先访问B的左子树D,访问D先访问D的左子树NULL,所以是:
NULL(left) -> D ->NULL(right) -> B -> NULL(right) -> A -> NULL(left) -> E -> NULL(right) -> C -> NULL(left) -> F -> NULL(right)

后序遍历(postorder Traversal)
左子树 -> 右子树 -> 根
NULL(left) -> NULL(right) -> D -> NULL(right) -> B ->NULL(left) -> NULL( right) -> E -> NULL(left) -> NULL(right) -> F -> C -> A

实现遍历

分置思想,大事化小小事化了

前序遍历

void PreOrder(BTNode *root)
{
    if(root == NULL)
    {
        printf("NULL ");
        return;
    }
    printf("%c ",root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}

本质就是遇到空或者调用完成后就会回到调用的地方,然后继续进行递归。

中序遍历

void InOrder(BTNode *root)
{
    if(!root)
    {
        printf("NULL ");
        return;
    }
    PreOrder(root->left);
    printf("%c ", root->data);
    PreOrder(root->right);
}

遇到了不为空的节点,先走这个节点的左子树,左子树不为空,再找左子树,直到为空,打印,然后往回走。只是因为顺序不同,打印的时机不同。

后序遍历

void PostOrder(BTNode *root)
{
    if (!root)
    {
        printf("NULL ");
        return;
    }
    PreOrder(root->left);
    PreOrder(root->right);
    printf("%c ", root->data);
}

二叉树oj题目

求二叉树节点的个数

如果我们用一遍遍历。在此我们用前序遍历写一下。


错误:

int BinaryTreeSize(BTNode *root)
{
    int num = 0;
    if (!root)
    {
        printf("NULL ");
        return;
    }
    num++;//这样是不对的。结合栈帧的知识,每次递归都是建立栈帧,出函数后会销毁
    //用静态的和用全局的都会出现多次调用存在问题。(不会销毁)
    BinaryTreeSize(root->left);
    BinaryTreeSize(root->right);
}

我们可以在main函数里创建一个变量,然后传变量的地址进去就可以了。

void BinaryTreeSize(BTNode *root, int *pn)
{
    if (!root)
    {
        return;
    }
    ++(*pn);
    BinaryTreeSize(root->left, pn);
    BinaryTreeSize(root->right, pn);
}

调用多次也不会出现问题。还有一种方式,返回值可以帮助我们解决:

int BinaryTreeSize(BTNode *root)
{
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

求叶子节点的个数

//求叶子节点的个数
int BinaryTreeLeafSize(BTNode* root)
{
    if(!root)
        return 0;
    if(root->left == NULL && root->right == NULL)
        return 1;

    return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

求第k层的节点个数

求树的第四层,可以求它的左子树的第三层加上右子树的第三层。也就是:

 BinaryTreeLevelKSize(root,k) =  BinaryTreeLevelKSize(root->left,k-1) +  BinaryTreeLevelKSize(root->right,k-1); 

所以我们又可以进行递归了。

int BinaryTreeLevelKSize(BTNode* root,int k)
{
    assert(k > 0);
    if(!root)
        return 0;
    if(k==1)
        return 1;
    //若root不为空,k也不等于1,那么说明第k层还在root的子树里面
    //转换成求左右子树的第k-1层的节点数量
    return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

二叉树的深度/高度

我们一般默认空树的高度是0。

都采用分置的思想,就是递归的思想,我们想求当前树的高度,就是去求左子树高度和右子树高度里取较大值然后加1。

int BinaryTreeDepth(BTNode *root)
{
    if(!root)
        return 0;
    //求较大值加1

    return 1 + (BinaryTreeDepth(root->left) > BinaryTreeDepth(root->right) ? BinaryTreeDepth(root->left) : BinaryTreeDepth(root->right));
}

但是这并非最优解(严谨说会造成严重的浪费),如果深度较大会导致溢出。因为每次都需要重复计算左子树和右子树的深度。我们如果先保存一下,就会防止浪费。

int BinaryTreeDepth(BTNode *root)
{
    //空树给0
    if(!root)
        return 0;
    int leftDepth = BinaryTreeDepth(root->left);
    int rightDepth = BinaryTreeDepth(root->right);
    //求较大值加1
    return 1 + (leftDepth > rightDepth ? leftDepth: rightDepth);
}

二叉树查找值为x的节点

在二叉树里面都必须要先判断root的空情况,如果不空, 再去进行判断。如果root->data存在而且不等于x,我们需要去左子树和右子树里面去找,如果左子树返回值不为空,那么说明找到了,直接返回这个左子树里面查找的返回值就可以。然后再进行右子树的查找。如果都没找到,返回空即可。

BTNode* BinaryTreeFind(BTNode* root,BTDataType x)
{
    if(!root)
        return NULL;
    if (root->data == x)
        return root;
    BTNode *leftNode = BinaryTreeFind(root->left, x);
    if (leftNode)
        return leftNode;
    BTNode *rightNode = BinaryTreeFind(root->right, x);
    if(rightNode)
        return rightNode;
    return NULL;
}

单值二叉树

如果二叉树的每个节点都具有相同的值,那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时,才返回true,否则false。

bool isUnivalTree(BTNode* root)
{
    if(!root)
        return true;
    if(root->left && root->left->data != root->data)
        return false;
    if(root->rigth && root->right->data != root->data)
        return false;
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

二叉树前序遍历并且把遍历过的值存储到数组中

数组a是我们malloc出来的,但是并不知道malloc多少内存,所以我们先写一个函数来计算树的节点个数。

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

要求前序遍历并储存值,我们可以先写一个前序遍历。其中pi是i的地址,i是数组的下标,用来将值储存在数组中。

int* _preorderTraversal(BTNode* root,int* a,int*pi)
{
    if(!root)
    {
        return;
    }
    a[(*pi)++] = root->data;
    _preorderTraversal(root->left,a,pi);
    _preorderTraversal(root->right, a,pi);
}

接下来我们只需要在要求的函数内部调用这个函数即可。注意一点,returnSize在我们的编写代码过程中并未用到,这是一个输出型参数,是因为我们在写OJ题目中,提交代码之后,服务器并不知道到底return的数组的size是多少,我们应该告诉服务器是多少。也就是在结束代码之前将returnSize的值修改成正确值。

int* preorderTraversal(BTNode* root,int* returnSize)
{
    int size = TreeSize(root);
    int *a = (int *)malloc(sizeof(int) * size);
    //应该检查,oj题目可以不用。
    int i = 0;
    _preorderTraversal(root, a, &i);
    *returnSize = size;
    return a;
}

在这里,很多小伙伴会犯这样的错误,写成如下的形式

int* _preorderTraversal(BTNode* root,int* a,int i)
{
    if(!root)
    {
        return;
    }
    a[(i)++] = root->data;
    _preorderTraversal(root->left,a,i);
    _preorderTraversal(root->right, a,i);
}

注意,这种情况下,结合我们之前学习的栈帧的知识,可以知道,每次递归都会创建新的函数栈帧,i是在新的函数栈帧中对于上一层递归中i的临时拷贝,出栈就会销毁。所以这个i并不能传递出本次递归,也就是对i的++是无效的。我们在这里应该传址调用,才能准确修改i的值。

相同的树

给两个二叉树的根节点p和q,编写一个函数来检查这两棵树是否相同。如果两棵二叉树的每个节点都相同,则视为相同。要求:

  • 形状相同
  • 值相同

如果解题,我们一般先考虑根相等与否,如果根相等,再去比较左子树相不相等,再去比较右子树相不相等。

bool isSameTree(BTNode* p,BTNode*q)
{
    if(!p && !q)
        return true;
    if(!p || !q)
        return false;
    if (p->data != q->data)
        return false;
    //&&是走完左边如果相等再走右边,如果左边值为0,则右边根本就不需要判断了。
    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

判断他的时间复杂度。考虑他的思想,每个数都比较一遍,这就是经典的O(N)。

对称二叉树/镜像二叉树

给定一个二叉树,检查它是否是镜像对称的。

可以这样做:先比较根,根都不相等那就必然不镜像对称。然后拿左的左树和右的右树比,拿左的右树根右的左树去比。

所以我们可以考虑一个子函数,用来比较根的左树和右树的镜像情况。先考虑好,传过去两个节点有以下几种情况:

  • 两个节点都为空,符合镜像关系
  • 两个节点有一个为空,不符合镜像关系
  • 两个节点都不为空,而且不等,不符合镜像关系
  • 两个节点都不为空,而且相等,符合镜像关系,我们需要继续递归。
bool _isSymmertric(BTNode *root1,BTNode* root2)
{
    if(!root1 && !root2)
        return true;
    if(!root1 || !root2)
        return false;
    if(root1->data != root2->data)
        return false;
    return _isSymmertric(root1->left, root2->right) && _isSymmertric(root1->right, root2->left);
}

写好子函数之后,主函数就好写了。

bool isSymmertric(BTNode *root)
{
    if(!root)
        return true;
    return _isSymmertric(root->left, root->right);
}

另一棵树的子树

给你两棵二叉树root和subRoot。检验root中是否包含和subRoot具有相同结构和节点值的子树。如果存在,返回true;否则,返回false。

二叉树tree的一棵子树包括trree的某个节点和这个节点的所有后代节点。tree也可以看作它自身的一棵子树。

思路就是拿root的每一棵子树来比较subroot。如何拿每一棵子树?遍历

bool isSubTree(BTNode* root,BTNode* subRoot)
{
    if(!root)
        return false;
    if(isSameTree(root,subRoot))
    {
        return ture;
    }
    return isSubTree(root->left, subRoot) || isSubTree(root->right, subRoot);
}

最好情况下的时间复杂度是O(N)查找一个就找到了,因为isSameTree需要遍历,为N。

最坏情况下的时间复杂度是O(N2),每个子树都比但是都最后没有通过。

层序遍历

把二叉树按照一层从左到右的顺序进行遍历。

思路就是用队列,先入根,出根的时候入根的左右孩子,左孩子出的时候入它的左右孩子。直到队列为空停止,遍历结束。

void BinaryTreeLevelOrder(BTNode *root)
{
    if (!root)
        return;
    Queue q;
    QueueInit(&q);
    //队列得放结构体指针才可以。否则找不到左右孩子
    while (QueueEmpty(&q))
    {
        BTNode *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");
}

这样我们就写好了层序遍历。用层序遍历我们就可以很好的判断二叉树是否为完全二叉树了。因为完全二叉树是除最后一层外全满,最后一排可能不满而且必须连续。我们层序遍历来判断即可。

判断是否为完全二叉树

之前的层序遍历是为空就不进了,现在我们空也进去,空出来的时候判断队列是否为空,队列为空则是,队列不为空则说明不连续,非完全二叉树。

int BinaryTreeComplete(BTNode* root)
{
        if (!root)
            return;
        Queue q;
        QueueInit(&q);
        //队列得放结构体指针才可以。否则找不到左右孩子
        while (QueueEmpty(&q))
        {
            BTNode *front = QueueFront(&q);
            QueuePop(&q);
            if(!front)
            {
                break;
            }
            else
            {
                QueuePush(&q, front->left);
                QueuePush(&q, front->right);
            }
        }
        BTNode* front = QueueFront(&q);
        // while (!front)
        // {
        //     //
        //     QueuePop(&q);
        //     front = QueueFront(&q);
        // }
        // if (front)
        //     {QueueDestroy(&q);
        //     return 0;}
        // return 1;

        while(!QueueEmpty(&q))
        {
            BTNode *front = QueueFront(&q);
            QueuePop(&q);

            if(front)
            {
                QueueDestroy(&q);
                return 0;
            }
        }
        QueueDestroy(&q);
        return 1;
} 

二叉树遍历

编写一个横序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。例如如下的先序遍历字符串:ABC##DE#G##F### 其中#表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

IO型题目:

#include<stdio.h>
#include<stdlib.h>
struct TreeNode
{
		struct TreeNode* left;
		struct TreeNode* right;
		char val;
}
strcut TreeNode* CreateTree(char* str, int* pi)
{
		if(str[*pi]=='#')
			{return NULL;
			(*pi)++;}
		struct TreeNode* root = malloc(sizeof(struct TreeNode));
		root->val = str[(*pi)++];
		root->left = CreateTree(str,pi);
		root->right = CreateTree(str,pi);
}
void InOrder(struct TreeNode* root)
{
		if(!root)
				return;
		InOrder(root->left);
		printf("%c ",root->left);
		InOrder(root->right);
}
int main()
{
    char str[100];
    scanf("%s",str);
  //多行输入
  //while(scanf("%s",str)!=EOF)
    int i = 0;

    struct TreeNode* root = CreateTree(str,&i);
    InOrder(root);
    return 0;
}

二叉树销毁

传二级指针可以直接置空,传一级指针也可以,只需要让调用者置空。就像用free一样。

void BinaryTreeDestroy(BTNode* root)
{
    if(!root)
        return;
    BinaryTreeDestroy(root->left);
    BinaryTreeDestroy(root->left);
    free(root);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值