代码随想录算法训练营第十三天 | 二叉树part01 | 基础概念、前中后序遍历的递归与非递归写法

二叉树概念

满二叉树

满二叉树:如果一棵二叉树只有子节点数量为0或2的结点,并且子节点数量为0的结点在同一层上,则这棵二叉树为满二叉树。

这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。 

完全二叉树 

 在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。

二叉搜索树 

二叉搜索树是有数值的了,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

下面这两棵树都是搜索树

平衡二叉搜索树

平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

 二叉树的存储方式

 指针,或数组

二叉树的遍历方式

  1. 深度优先遍历:先往深走,遇到叶子节点再往回走。前序遍历、中序遍历、后序遍历的区别在于根节点遍历的顺序
    1. 前序遍历:中左右
    2. 中序遍历:左中右
    3. 后序遍历:左右中
  2. 广度优先遍历:一层一层的去遍历。

C语言中的二叉树节点结构体类型定义

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

二叉树的递归遍历

递归算法三要素:

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定每一层递归的逻辑

前序遍历

以下假设二叉树中最多只有100个节点

void subPreorder(struct TreeNode* root, int* ret, int* returnSize)
{
    if(root != NULL)
    {
        ret[(*returnSize)++] = root->val;
        subPreorder(root->left, ret, returnSize);
        subPreorder(root->right, ret, returnSize);
    }
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int) * 100);
    *returnSize = 0;
    subPreorder(root, ret, returnSize);
    return ret;
}

中序遍历 

void subInorder(struct TreeNode* root, int* ret, int* returnSize)
{
    if(root != NULL)
    {
        subInorder(root->left, ret, returnSize);
        ret[(*returnSize)++] = root->val;
        subInorder(root->right, ret, returnSize);
    }
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int) * 100);
    *returnSize = 0;
    subInorder(root, ret, returnSize);
    return ret;
}

后续遍历 

void subPostorder(struct TreeNode* root, int* ret, int* returnSize)
{
    if(root != NULL)
    {
        subPostorder(root->left, ret, returnSize);
        subPostorder(root->right, ret, returnSize);
        ret[(*returnSize)++] = root->val;
    }
}

int* postorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int) * 100);
    *returnSize = 0;
    subPostorder(root, ret, returnSize);
    return ret;
}

非递归写法

前序遍历

遍历节点时,即时处理将节点的值加入结果,接着先将右节点入栈再将左节点入栈,这样当取栈顶元素时是先处理左节点

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int) * 100);
    struct TreeNode* nodes[100];
    struct TreeNode* temp;    
    int nodesSize = 0;

    if(root != NULL)
    {
        nodes[nodesSize++] = root;
    }
    *returnSize = 0;
    while(nodesSize != 0)
    {
        temp = nodes[--nodesSize];
        ret[(*returnSize)++] = temp->val;
        if(temp->right != NULL)
        {
            nodes[nodesSize++] = temp->right;
        }
        if(temp->left != NULL)
        {
            nodes[nodesSize++] = temp->left;
        }

    }
    return ret;
}

中序遍历

有左节点就一直先处理左节点,并将遍历过的节点不处理直接入栈

当当前节点为空时,代表一路下来的左节点都已入栈,此处取出栈顶元素,将节点的值加入结果,将右节点加入栈,重复上一步操作

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int)*100);
    struct TreeNode* nodes[100];
    struct TreeNode* curr = root;
    int nodesSize = 0;
    *returnSize = 0;


    while((curr != NULL) || (nodesSize != 0))
    {
        if(curr != NULL)
        {
            nodes[nodesSize++] = curr;
            curr = curr->left;
        }
        else
        {
            curr = nodes[--nodesSize];
            ret[(*returnSize)++] = curr->val;
            curr = curr->right;
        }
    }
    return ret;
}

后序遍历

有点绕,逻辑见代码注释

int* postorderTraversal(struct TreeNode* root, int* returnSize){
    int* ret = (int*)malloc(sizeof(int)*100);
    struct TreeNode* nodes[100];
    int nodesSize = 0;
    struct TreeNode* curr = root;
    struct TreeNode* preNode;

    *returnSize = 0;

    while((curr != NULL) || (nodesSize != 0))
    {
        /* 新拿到一个节点,先一直向左节点遍历, 将经过的节点入栈 */
        while(curr != NULL)
        {
            nodes[nodesSize++] = curr;
            curr = curr->left;;
        }

        /* 程序运行到这一步时curr为空,取出栈顶指针 */
        curr = nodes[--nodesSize];
        /* 程序运行到这一步时,curr节点的左节点一定为NULL或已经处理过, 处理curr及其右节点 */
        if(curr != NULL)
        {
            if(curr->right == NULL) /* 若当前右节点为空,直接将值接入结果, 记录本节点处理过 */
            {
                ret[(*returnSize)++] = curr->val;
                preNode = curr;
                curr = NULL;
            }
            else if(curr->right == preNode) /* 若右节点也已经处理过,直接将值接入结果, 记录本节点处理过 */
            {
                ret[(*returnSize)++] = curr->val;
                preNode = curr;
                curr = NULL;
            }
            else /* 若右节点不为空,且未处理过,将当前节点放回栈里,进入右节点进行处理 */
            {
                nodes[nodesSize++] = curr;
                curr = curr->right;
            }
        }
    }
    return ret;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当然,二叉树的遍历有三种主要方式:先序遍历(根-左-右)、中序遍历(左-根-右)和后序遍历(左-右-根)。非递归的层次遍历(也叫广度优先遍历,从上到下、从左到右)通常使用队列来辅助实现。 这里分别给出这些遍历的非递归算法代码: 1. 层序遍历(广度优先遍历): ```c #include <stdio.h> #include <stdlib.h> #include <queue> struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; void levelOrder(struct TreeNode* root) { if (root == NULL) return; // 使用队列存储每一层的节点 queue<struct TreeNode*> q; q.push(root); while (!q.empty()) { int size = q.size(); for (int i = 0; i < size; i++) { struct TreeNode* node = q.front(); q.pop(); printf("%d ", node->val); // 打印当前节点值 if (node->left != NULL) q.push(node->left); if (node->right != NULL) q.push(node->right); } printf("\n"); // 换行表示新的一层 } } ``` 2. 先序遍历(递归非递归两种方式,这里是非递归版本,使用栈): ```c void preorderNonRecursive(struct TreeNode* root) { if (root == NULL) return; stack<struct TreeNode*> s; s.push(root); while (!s.empty()) { struct TreeNode* node = s.top(); s.pop(); printf("%d ", node->val); // 打印当前节点值 if (node->right != NULL) s.push(node->right); if (node->left != NULL) s.push(node->left); } } ``` 3. 中序遍历(非递归,同样使用栈): ```c void inorderNonRecursive(struct TreeNode* root) { if (root == NULL) return; stack<struct TreeNode*> s; struct TreeNode* curr = root; while (curr != NULL || !s.empty()) { while (curr != NULL) { s.push(curr); curr = curr->left; } curr = s.top(); s.pop(); printf("%d ", curr->val); // 打印当前节点值 curr = curr->right; } } ``` 4. 后序遍历非递归,使用两个栈): ```c void postorderNonRecursive(struct TreeNode* root) { if (root == NULL) return; stack<struct TreeNode*> s1, s2; s1.push(root); while (!s1.empty()) { struct TreeNode* node = s1.top(); s1.pop(); s2.push(node); if (node->left != NULL) s1.push(node->left); if (node->right != NULL) s1.push(node->right); } while (!s2.empty()) { struct TreeNode* node = s2.top(); s2.pop(); printf("%d ", node->val); // 打印当前节点值 } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值