915树代码

若图片无法查看,请进入完整链接查看!

二叉树的节点定义

struct TreeNode 
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x),left(left), right(right) {}
};

递归前序遍历:

void inorder(TreeNode *root)
{
    if(!root) return;
    cout << root->val << ' ';
    inorder(root->left);
    inorder(root->right);
}

递归前序遍历的步骤:

  1. 如果根节点为 null,直接返回。

  2. 否则,先打印当前根节点的值。

  3. 递归调用前序遍历函数,遍历左子树。

  4. 递归调用前序遍历函数,遍历右子树。

这样实现的前序遍历顺序就是:根结点 -> 左子树 -> 右子树。

递归的思想是:每个子问题(子树)都跟原问题(整颗树)一样,都可以使用同样的前序遍历方式来解决。

所以通过递归来实现前序遍历可以很简单地访问每一个节点,同时还省去了使用外部数据结构如栈来模拟递归调用栈的步骤。它利用函数调用栈本身就实现了前序遍历的逻辑。

非递归的前序遍历:

void preorder2(TreeNode *root)
{
    stack<TreeNode*> s;
    s.push(root);
    while (!s.empty())
    {
        TreeNode *t = s.top();
        s.pop();

        cout << t->val << ' ';
        if (t->right) s.push(t->right);
        if (t->left) s.push(t->left);
    }
}

前序遍历的非递归实现步骤:

  1. 将根节点压入栈

  2. 从栈pop出一个节点node

  3. 输出node的值

  4. 将node的右子节点(如果有)Push到栈

  5. 将node的左子节点(如果有)Push到栈

  6. 重复2-5步,直到栈为空

非递归实现的关键是用栈按照前序遍历顺序保存节点,并按后进先出的顺序访问这些节点。

每次从栈中pop出的节点即为当前遍历的节点,这样可以模拟调用栈的行为来实现前序遍历,从而避免使用递归。

这与递归实现的区别是,非递归使用了外部的数据结构栈来保存节点的遍历顺序,而递归直接利用函数调用栈来实现。

递归中序遍历:

void inorder(TreeNode *root)
{
    if(!root) return;
    inorder(root->left);
    cout << root->val << ' ';
    inorder(root->right);
}

中序遍历的实现步骤:

1.如果根节点为 null,直接返回。

2.否则递归调用 inorder,遍历左子树。

3.输出当前根节点的值。

4.递归调用 inorder,遍历右子树。

这样的顺序就实现了:左子树 -> 根节点 -> 右子树,即中序遍历。

与前序遍历不同的是,中序遍历先递归处理左子树,然后输出根节点,再处理右子树。

同样使用递归可以省去外部数据结构,直接利用函数调用栈来保存遍历顺序。每个子问题都可以通过中序遍历的逻辑来解决。

这样一步步深入左子树,然后输出节点,再深入右子树,就完成了整个二叉树的中序遍历。

非递归中序遍历:

void inorder2(TreeNode* root)
{
    stack<TreeNode*> stk;
    TreeNode* curr = root;

    while(curr || !stk.empty())
    {
        // 把当前节点的左子节点全部压入栈
        while(curr)
        {
            stk.push(curr);
            curr = curr->left;
        }

        // 从栈中弹出一个节点计算
        curr = stk.top();
        stk.pop();

        // 输出节点
        cout << curr->val << " ";

        // 让当前节点指向右子节点
        curr = curr->right;
    }
}

实现步骤:

  1. 初始化当前节点curr为根节点,栈stk为空。

  2. 如果curr节点不为空,或者栈不为空,进入循环。

  3. 如果curr不为空,遍历它的左子树,将节点全部压入栈。

  4. 当前节点为栈顶元素,出栈输出。

  5. 更新当前节点为该节点的右子节点。

  6. 重复2-5步,直到栈和curr都为空,遍历结束。

通过栈保存遍历顺序,先把左子树全部入栈,出栈时从左到右依次处理节点,实现了中序遍历的迭代逻辑。
利用栈模拟递归调用栈,可以非递归地完成二叉树的中序遍历。

递归后序遍历:

void postorder(TreeNode *root)
{
    if(!root) return;
    postorder(root->left);
    postorder(root->right);
    cout << root->val << ' ';
}

后序遍历的实现步骤:

  1. 如果根节点为null,直接返回

  2. 否则递归调用postorder函数,遍历左子树

  3. 递归调用postorder函数,遍历右子树

  4. 输出当前根节点的值

这样的顺序实现了:左子树 -> 右子树 -> 根节点,即后序遍历。

与前序和中序不同的是,后序遍历先递归处理左右子树,最后输出根节点。

和前两种遍历一样,递归方式直接利用函数调用栈,可以省去使用外部数据结构,以递归逻辑实现后序遍历。

一个个地深入左子树,然后深入右子树,最后输出根节点,就完成了整颗二叉树的后序遍历。

后序非递归遍历:

void postorder2(TreeNode* root)
{
    stack<TreeNode*> stk;
    TreeNode* curr = root;
    TreeNode* prev = NULL;

    while(curr || !stk.empty())
    {
        // 将当前节点及其左节点全部入栈
        while(curr)
        {
            stk.push(curr);
            curr = curr->left;
        }

        // 如果栈顶没有右子节点或右子节点已经访问完
        curr = stk.top();
        if(!curr->right || curr->right == prev)
        {
            stk.pop();
            cout << curr->val << " ";
            prev = curr;
            curr = nullptr;
        }
        else // 有右子节点且还未访问
            curr = curr->right;
    }
}

实现思路:

  1. 将当前节点入栈,并遍历其左子树入栈

  2. 如果栈顶节点没有右子节点或右子节点访问完毕,则输出该节点

  3. 否则将当前节点指向右子节点

利用两个指针curr和prev,和一个标记是否访问过右子节点,可以完成后序遍历的迭代过程。

与递归中顺序相反,但依然可以输出后序序列。

层次遍历:

void levelOrder(TreeNode *root)
{
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty())
    {
        TreeNode *t = q.front();
        q.pop();

        cout << t->val << ' ';
        if (t->left) q.push(t->left);
        if (t->right) q.push(t->right);
    }
}

算法流程:

  1. 使用STL中的queue来实现队列的数据结构

  2. 首先将根节点入队

  3. 循环从队首取出节点

    3.1 打印当前节点的值

    3.2 如果左节点不为空,入队左节点

    3.3 如果右节点不为空,入队右节点

  4. 重复3直到队列为空,这样一个层次一个层次地把整个二叉树遍历完即实现层次遍历。

关键点是利用队列的先入先出特性,可以按照各层的顺序依次处理节点,从而实现层次遍历。

王道课后题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include <iostream>
#include <queue>
#include <stack>

using namespace std;

struct TreeNode
{
    char val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(char x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(char x, TreeNode *left, TreeNode *right) : val(x),left(left), right(right) {}
};

void invertLevelOrder(TreeNode *root)
{
    queue<TreeNode*> q;
    stack<TreeNode*> s;
    q.push(root);
    while (!q.empty())
    {
        TreeNode *t = q.front();
        q.pop();

        s.push(t);
        if (t->left) q.push(t->left);
        if (t->right) q.push(t->right);
    }

    while (s.size())
    {
        cout << s.top()->val << ' ';
        s.pop();
    }
}

int main()
{
    TreeNode *root = new TreeNode('A',
                                  new TreeNode('B',
                                               new TreeNode('D'),
                                               new TreeNode('E')),
                                  new TreeNode('C'));
    invertLevelOrder(root);

    return 0;
}

输出:E D C B A

构建的树的形状如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 递归求树的高度
int height(TreeNode *root)
{
    if (!root) return 0;
    return max(height(root->left), height(root->right)) + 1;
}
// 非递归求树的高度
int height2(TreeNode *root)
{
    queue<TreeNode*> q;
    int height = 0;
    q.push(root);
    while (q.size())
    {
        int n = q.size();   // 当前层的结点个数
        while (n --)
        {
            TreeNode *t = q.front();
            q.pop();
            if (t->left) q.push(t->left);
            if (t->right) q.push(t->right);
        }
        height ++;
    }

    return height;
}

算法步骤:

  1. 使用队列queue来层序遍历二叉树。
  2. 将根节点入队。
  3. 每遍历一层,计算当前层节点数量n,进行n次出队操作。
  4. 每出队一次,如果节点有左子节点,入队左子节点。如果节点有右子节点,入队右子节点。
  5. 每层遍历结束,height++。
  6. 循环结束时,height即为二叉树的高度。

关键点是使用队列实现层序遍历,每层节点入队出队,可以非递归实现二叉树的高度计算。时间复杂度O(n),空间复杂度O(n)。

该算法还可用来求树的最大宽度每层节点个数

二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法。

完全二叉树的定义是:除了最后一层外,其他层的节点都必须是满的,并且最后一层的节点都靠左排列。

为了判断给定的二叉树是否是完全二叉树,我们可以使用层次遍历的方式来进行判断。

bool isCompleteTree(TreeNode *root)
{
    queue<TreeNode*> q;
    q.push(root);
    bool leaf = false;  // 是否遇到叶子节点
    while (q.size())
    {
        TreeNode *t = q.front();
        q.pop();

        // 若之前已遇到叶节点,且当前节点还有子节点,返回false
        if (leaf && (t->left || t->right)) return false;

        // 若遇到叶节点
        if (!t->left && !t->right) leaf = true;
        // 若左子树为空且右子树不空
        if (!t->left && t->right) return false;

        if (t->left) q.push(t->left);
        if (t->right) q.push(t->right);
    }

    return true;
}

算法思路:

  1. 使用队列层次遍历二叉树
  2. 用一个标记leaf记录是否遇到过叶子节点
  3. 如果已经遇到叶子节点,则后续所有节点都应该是叶子节点, 否则返回false
  4. 如果遇到左子树为空且右子树不空,返回false
  5. 遍历完仍然返回true,则表明满足完全二叉树性质

时间复杂度O(n),空间复杂度O(n)。

完整代码:

#include <iostream>
#include <queue>
#include <stack>

using namespace std;

struct TreeNode
{
    char val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(char x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(char x, TreeNode *left, TreeNode *right) : val(x),left(left), right(right) {}
};

bool isCompleteTree(TreeNode *root)
{
    queue<TreeNode*> q;
    q.push(root);
    bool leaf = false;  // 是否遇到叶子节点
    while (q.size())
    {
        TreeNode *t = q.front();
        q.pop();

        // 若之前已遇到叶节点,且当前节点还有子节点,返回false
        if (leaf && (t->left || t->right)) return false;

        // 若遇到叶节点
        if (!t->left && !t->right) leaf = true;
        // 若左子树不空且右子树空
        if (!t->left && t->right) return false;

        if (t->left) q.push(t->left);
        if (t->right) q.push(t->right);
    }

    return true;
}

int main()
{
    TreeNode *root = new TreeNode('A',
                                  new TreeNode('B',
                                               new TreeNode('D'),
                                               new TreeNode('E')
                                               ),
                                  new TreeNode('C'));
    cout << isCompleteTree(root) << endl;

    return 0;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include <iostream>
#include <queue>
#include <stack>

using namespace std;

struct TreeNode
{
    char val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(char x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(char x, TreeNode *left, TreeNode *right) : val(x),left(left), right(right) {}
};

int res = 0;
void dfs(TreeNode *root)
{
    if (!root) return;
    if (root->left && root->right) res ++;

    dfs(root->left);
    dfs(root->right);
}

// 第二种做法,类似王道参考答案
int get(TreeNode *root)
{
    if (!root) return 0;

    if (root->left && root->right)
        return get(root->left) + get(root->right) + 1;
    return get(root->left) + get(root->right);
}

int main()
{
    TreeNode *root = new TreeNode('A',
                                  new TreeNode('B',
                                               new TreeNode('D'),
                                               new TreeNode('E')
                                               ),
                                  new TreeNode('C'));

    dfs(root);
    cout << res << endl;
    cout << get(root) << endl;

    return 0;
}

设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左、右子树进行交换的函数。

void swapLeftRight(TreeNode* root) {
    if(root == nullptr) return;

    swap(root->left, root->right);

    swapLeftRight(root->left);
    swapLeftRight(root->right);
}

函数做以下操作:

  1. 递归地遍历整棵树。

  2. 对每个结点,暂时保存left子节点指针,然后让right子节点作为新的left子节点,原left子节点作为新的right子节点。

  3. 这样就实现了这个结点的左右子树互换。

  4. 之后递归调用这个函数,继续操作左右子节点,从而完成整棵树所有结点的左右子树交换。

关键点是:

  • 递归遍历所有结点
  • 对每个结点使用第三个变量临时存储left,实现left和right的交换
  • 交换后递归处理交换后的左右子树

这样就可以在O(N)时间内完成整棵树结点左右子树的交换,而不需要额外的空间。

假设二叉树采用二叉链存储结构存储,设计一个算法,求先序遍历序列中第 k(1≤k≤二叉树中结点个数)个结点的值。

#include <iostream>
#include <queue>
#include <stack>

using namespace std;

struct TreeNode
{
    char val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(char x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(char x, TreeNode *left, TreeNode *right) : val(x),left(left), right(right) {}
};

// 求先序遍历序列中第k个结点的值
int res = 0, k = 4, i = 0;
void dfs(TreeNode* root)
{
    i ++;
    if (i == k) res = root->val;
    if (root->left) dfs(root->left);
    if (root->right) dfs(root->right);
}

int main()
{
    TreeNode *root = new TreeNode('A',
                                  new TreeNode('B',
                                               new TreeNode('D'),
                                               new TreeNode('E')
                                               ),
                                  new TreeNode('C'));

    dfs(root);
    cout << (char)res << endl; // E
    return 0;
}

已知二叉树以二叉链表存储,编写算法完成:对于树中每个元素值为x的结点,删去以它为根的子树

dfs和bfs都可以做,这里给出dfs的做法

void dfs(TreeNode *root, char x)
{
    if (!root) return;
    if (root->val == x) root = NULL;
    else
    {
        cout << root->val << ' ';
        dfs(root->left, x);
        dfs(root->right, x);
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 返回根结点到p的路径
vector<TreeNode *> findPath(TreeNode *root, TreeNode *p)
{
    vector<TreeNode*> s;
    TreeNode *cur = root, *pre = NULL;
    while (cur || s.size())
    {
        while (cur)
        {
            s.push_back(cur);
            cur = cur->left;
        }

        cur = s.back();
        if (cur->right && cur->right != pre)
            cur = cur->right;
        else
        {
            s.pop_back();
            if (cur == p)
                return s;
            pre = cur;
            cur = NULL;
        }
    }
}

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
{
    vector<TreeNode *> path1 = findPath(root, p);
    vector<TreeNode *> path2 = findPath(root, q);
    for (int i = 0; ; i ++)
        if (path1[i] != path2[i])
            return path1[i - 1];
    return NULL;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值