C语言经典算法之二叉树的后序线索化

目录

前言

A.建议

B.简介

一 代码实现

二 时空复杂度

A.时间复杂度分析:

B.空间复杂度分析:

三 优缺点

A.优点:

B.缺点:

四 实现中的应用


前言

A.建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

tips:文中的对数均以2为底数】

B.简介

后序线索化是将二叉树按照后序遍历的顺序进行线索化的过程。线索化的目的是为了在遍历时能够更高效地访问节点。

一 代码实现

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

// 定义二叉树节点结构
struct TreeNode {
    int data;
    struct TreeNode* left;
    struct TreeNode* right;
    int isThreaded;  // 标记是否为线索
};

// 创建新的二叉树节点
struct TreeNode* createNode(int data) {
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    if (newNode == NULL) {
        perror("Memory allocation failed");
        exit(EXIT_FAILURE);
    }

    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    newNode->isThreaded = 0;

    return newNode;
}

// 后序线索化函数
void createPostorderThread(struct TreeNode* root, struct TreeNode** prev) {
    if (root == NULL) {
        return;
    }

    // 递归线索化左子树
    createPostorderThread(root->left, prev);

    // 递归线索化右子树
    createPostorderThread(root->right, prev);

    // 处理当前节点
    if (*prev != NULL) {
        if ((*prev)->right == NULL) {
            // 如果前驱节点的右指针为空,将其设置为当前节点
            (*prev)->right = root;
            (*prev)->isThreaded = 1;
        }
    }

    // 更新前驱节点
    *prev = root;
}

// 后序遍历线索化二叉树
void postorderThreadedTraversal(struct TreeNode* root) {
    struct TreeNode* current = root;

    while (current != NULL) {
        // 移动到最左子树
        while (current->left != NULL && current->isThreaded == 0) {
            current = current->left;
        }

        // 如果右子树为空或是线索化的,或者当前节点是叶子节点
        if (current->right == NULL || current->isThreaded == 1 || (current->left == NULL && current->right == NULL)) {
            printf("%d ", current->data);
            current = current->right;
        } else {
            // 否则,移动到右子树的根节点
            current = current->right;
        }
    }
}

int main() {
    // 创建示例二叉树
    struct TreeNode* root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);

    // 执行后序线索化
    struct TreeNode* prev = NULL;
    createPostorderThread(root, &prev);

    // 执行后序遍历线索化二叉树
    printf("Postorder Threaded Traversal: ");
    postorderThreadedTraversal(root);
    printf("\n");

    return 0;
}

这个例子中,createPostorderThread 函数负责后序线索化,而 postorderThreadedTraversal 函数实现了线索化二叉树的后序遍历。后序线索化的思想类似于中序线索化,只是遍历的顺序不同。

二 时空复杂度

A.时间复杂度分析:


创建二叉树节点:时间复杂度O(1)(常数时间),因为只是为每个节点分配内存。

后序线索化函数 (createPostorderThread):每个节点都只会被访问一次。对于每个节点,进行常数次操作(检查前驱节点的右指针、设置前驱节点的右指针等)。因此,总体时间复杂度为 O(n),其中 n是二叉树中的节点数量。


后序遍历线索化二叉树函数 (postorderThreadedTraversal):每个节点最多被访问两次:一次从左子树移动到当前节点,一次从当前节点移动到右子树。对于每个节点,进行常数次操作。总体时间复杂度为O(n),其中n是二叉树中的节点数量。


主函数 (main):创建示例二叉树,线索化并后序遍历的时间复杂度为O(n)

因此,总体时间复杂度为 O(n) + O(n) = O(n)


B.空间复杂度分析:


递归调用栈:递归调用 createPostorderThread 和 postorderThreadedTraversal 函数时会使用调用栈。由于这是后序遍历,递归深度最多为树的高度(h),最坏情况下为O(h)


额外空间:除了递归调用栈之外,算法的额外空间消耗主要是用于存储前驱节点指针。
由于前驱节点指针的数量与节点数量成正比,所以空间复杂度为O(n)


因此,整个算法的空间复杂度为O(h) + O(n)。在最坏情况下,当树为链状结构时,h = n,因此空间复杂度为O(n)

三 优缺点

A.优点:


线性时间复杂度: 算法实现了对二叉树的后序遍历的线索化,使得后序遍历的时间复杂度为 O(n),其中 n 是二叉树中的节点数量。在不使用线索化的情况下,后序遍历的时间复杂度为 O(n^2),因此线索化提供了明显的效率优势。

节省空间: 通过使用线索化,避免了使用递归或额外的栈空间,从而减少了空间的开销。线索化使得遍历变得更加简洁,不需要维护额外的数据结构来跟踪遍历过程。

通用性: 与中序线索化一样,后序线索化的实现是通用的,适用于任意二叉树,而不仅仅是二叉搜索树。这使得该算法具有一定的通用性。

B.缺点:


不支持动态插入和删除节点: 该算法仅在线索化时对树进行修改,不支持在后续遍历线索化后动态插入或删除节点。如果需要支持这些操作,可能需要额外的处理。

可读性较差: 使用了指针的操作和嵌套循环,使得代码的可读性相对较差。理解算法的实现需要对二叉树遍历和线索化有一定的了解。

错误处理不足: 在内存分配失败时,使用 perror 和 exit 终止程序,但这种方式并不友好,它直接终止整个程序而不提供清理资源的机会。更好的做法是在错误发生时返回错误代码或采用其他错误处理策略。

特定应用场景: 后序线索化适用于需要频繁进行后序遍历的情况,但在一些实际应用中,对于特定的问题,可能有其他更为高效的数据结构和算法。
 

四 实现中的应用

后序线索化的应用相对较少,因为后序遍历在一些实际场景中使用较少,而中序遍历的线索化更为常见。然而,仍然有一些可能的应用场景:

表达式树的求值: 后序线索化可用于表达式树的求值。表达式树是一种将表达式表示为树形结构的方式,后序遍历的结果可以直接用于求解表达式的值。

树形结构的计算: 在某些树形结构中,需要在计算节点值时先计算子节点的值,然后再计算父节点的值。后序线索化提供了一种按照这个计算顺序遍历树的方法。

树形结构的输出: 对于一些需要按照特定格式输出树节点值的应用,后序线索化提供了一种按照后序遍历顺序输出节点值的方式,从而方便处理输出逻辑。

数据库索引: 在某些数据库索引结构中,需要在遍历时按照某种特定的顺序进行操作。虽然后序线索化在这方面应用相对较少,但在特定场景下也可能发挥一定作用。

特定场景的遍历优化: 对于一些特定的应用场景,可能需要在二叉树上进行后序遍历的操作。后序线索化可以在这些场景中提供遍历的优化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JJJ69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值