数据结构与算法:二叉树与广义表

ACM大牛带你玩转算法与数据结构-课程资料

 本笔记属于船说系列课程之一,课程链接:

哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/cheese/play/ep66799?csource=private_space_class_null&spm_id_from=333.999.0.0

你也可以选择购买『船说系列课程-年度会员』产品『船票』,畅享一年内无限制学习已上线的所有船说系列课程:船票购买入口icon-default.png?t=N7T8https://www.bilibili.com/cheese/pages/packageCourseDetail?productId=598

做题网站OJ:HZOJ - Online Judge

Leetcode :力扣 (LeetCode) 全球极客挚爱的技术成长平台

二叉树:广义表表示法

  二叉树转广义表:

空树用()表示

现在有一个节点A可以表示为A或A()

现在节点A又一个左节点,左节点为B,那么可以表示为,A(B,)或A(B)

如果为右节点,表示为A(,B)

如果左右都有节点表示为A(B,C)

来一个例子,如下图:

先对根节点进行表示A(,)

然后先进行对他的左子树表示A(B(,),)

然后继续左子树往下表示A(B(,D), )

然后左子树没有了,开始表示根节点的右子树A(B(,D),C(,))

继续往下进行表示A(B(,D),C(E,F))

最终结果:

A(B(,D),C(E,F))

上面就是对二叉树转换为广义表的形式的方法,下面是代码实现:


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

typedef struct Node {
    int key;
    struct Node *lchild, *rchild;
} Node;

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NULL;
    return p;
}
//插入节点
Node *insert(Node *root, int key) {
    if (!root) return getNewNode(key);
    if (rand() % 2) root->lchild = insert(root->lchild, key);
    else root->rchild = insert(root->rchild, key);
    return root;
}

//随机生成二叉树函数
Node *getRandomBinaryTree(int n) {
    Node *root = NULL;
    for (int i = 0; i < n; i++) {
        root = insert(root, rand() % 100);
    }
    return root;
}


void clear(Node *root) {
    if (!root) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

char buff[1000];
int len = 0;
//二叉树转广义表
//二叉树转广义表和前序遍历差不多
//所以可以按着前序遍历进行处理
void __serialize(Node *root) {
    if (!root) return ;
    //处理根节点,先将根节点进行表示
    len += sprintf(buff + len, "%d", root->key);
    //然后判断当前节点是否还有左右子树
    if (!root->lchild && !root->rchild) return ;
    //到了这里说明有左子树或者有右子树
    //那么就需要括号,先输入进左括号
    len += sprintf(buff + len, "(");
    //递归左子树
    __serialize(root->lchild);
    //判断是否有右子树
    if (root->rchild) {
        //有右子树就需要 ‘,‘来隔开进行表示右子树
        len += sprintf(buff + len, ",");
        __serialize(root->rchild);
    }
    //最后说明当前左右子树递归完成,用右)进行包起来
    len += sprintf(buff + len, ")");
    return ;
}

void pre_out(Node *root) {
    if (!root) return ;
    printf("%d ", root->key);
    pre_out(root->lchild);
    pre_out(root->rchild);
    return ;
}

void serialize(Node *root) {
    memset(buff, 0, sizeof(buff));
    len = 0;
    __serialize(root);
    return ;
}

int main() {
    srand(time(0));
    #define MAX_NODE 10
    Node *root = getRandomBinaryTree(MAX_NODE);
    serialize(root);
    printf("%s\n", buff);
    pre_out(root);
    putchar(10);
    return 0;
}

广义表转二叉树:

当前有一个广义表A(B, C)

现在可以这样来看这个广义表:

然后我们是先要处理根节点,然后在处理左子树,在处理右子树对吧。

通过图中我们先处理A,然后把A转换为一个节点,

 

然后就是'(',这里遇到了'('我们就应该知道他是有子树的,

然后再是B就需要处理为左子树,因为没有遇到','所以可以判断当前B为左子树

然后是','说明后面如果遇到的不是')',而这里下一个就是C,那么C就是右子树

最后遇到')'说明当前的根节点处理完成。

对于上面的处理流程,结合之前学习的数据结构,可以判断出和之前学习的栈结构一样,那么对于当前需要广义表转二叉树我们可以用栈来进行辅助处理:

  1. 遇到关键字,生成新节点,比如遇到A,生成节点A
  2. 遇到(,将刚才的新节点压入到栈中,刚刚生成的A节点压入到栈中,并设置标记为左子树
  3. 遇到, ,标记当前处理的是右子树
  4. 遇到),将栈顶节点出战
  5. 每生成新节点,根据标记设置左右子树

代码如下:

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

#define MAX_NODE 10

typedef struct Node {
    int key;
    struct Node *lchild, *rchild;
} Node;

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->lchild = p->rchild = NULL;
    return p;
}
//插入节点
Node *insert(Node *root, int key) {
    if (!root) return getNewNode(key);
    if (rand() % 2) root->lchild = insert(root->lchild, key);
    else root->rchild = insert(root->rchild, key);
    return root;
}

//随机生成二叉树函数
Node *getRandomBinaryTree(int n) {
    Node *root = NULL;
    for (int i = 0; i < n; i++) {
        root = insert(root, rand() % 100);
    }
    return root;
}


void clear(Node *root) {
    if (!root) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

char buff[1000];
int len = 0;
//二叉树转广义表
//二叉树转广义表和前序遍历差不多
//所以可以按着前序遍历进行处理
void __serialize(Node *root) {
    if (!root) return ;
    //处理根节点,先将根节点进行表示
    len += sprintf(buff + len, "%d", root->key);
    //然后判断当前节点是否还有左右子树
    if (!root->lchild && !root->rchild) return ;
    //到了这里说明有左子树或者有右子树
    //那么就需要括号,先输入进左括号
    len += sprintf(buff + len, "(");
    //递归左子树
    __serialize(root->lchild);
    //判断是否有右子树
    if (root->rchild) {
        //有右子树就需要 ‘,‘来隔开进行表示右子树
        len += sprintf(buff + len, ",");
        __serialize(root->rchild);
    }
    //最后说明当前左右子树递归完成,用右)进行包起来
    len += sprintf(buff + len, ")");
    return ;
}

void pre_out(Node *root) {
    if (!root) return ;
    printf("%d ", root->key);
    pre_out(root->lchild);
    pre_out(root->rchild);
    return ;
}

void serialize(Node *root) {
    memset(buff, 0, sizeof(buff));
    len = 0;
    __serialize(root);
    return ;
}

Node *deserialize(char *buff, int n) {
    //创建栈进行辅助操作
    Node **arr = (Node **)malloc(sizeof(Node *) * MAX_NODE);
    //scode表示用来表示当前执行流程
    //flag 0 表示当前处理左子树,1表示处理右子树
    int top = -1, flag = 0, scode = 0;
    Node *p = NULL, *root = NULL;
    for (int i = 0; buff[i]; i++) {
        switch(scode) {
            case 0:  {
                //当前位置为数字时设置为状态1,处理新节点
                if (buff[i] >= '0' && buff[i] <= '9') scode = 1;
                //当前位置为左括号时设置为状态2,入栈当前位置的根节点,修改标记为左子树
                else if (buff[i] == '(') scode = 2;
                //为逗号时设置状态3,标记当前为右子树
                else if (buff[i] == ',') scode = 3;
                //为右括号设置状态4,弹栈
                else scode = 4;
                //因为0状态没有对当前字符做任何处理所以需要回溯循环
                i -= 1;
            } break;
            case 1:  {
                int num = 0;
                while (buff[i] <= '9' && buff[i] >= '0') {
                    num = num * 10 + (buff[i] - '0');
                    i += 1;
                }
                p = getNewNode(num);
                if (top >= 0 && flag == 0) arr[top]->lchild = p;
                if (top >= 0 && flag == 1) arr[top]->rchild = p;
                i--;
                scode = 0;
            } break; 
            case 2: {
                arr[++top] = p;
                flag = 0;
                scode = 0;
            } break;
            case 3: {
                flag = 1; 
                scode = 0;
            } break;
            case 4: {
                root = arr[top--];
                scode = 0;
            } break;
        }
    }

    return p;
}

int main() {
    srand(time(0));
    Node *root = getRandomBinaryTree(MAX_NODE);
    serialize(root);
    printf("%s\n", buff);
    pre_out(root);
    putchar(10);
    Node *new_root = deserialize(buff, len);
    pre_out(root);
    putchar(10);
    return 0;
}

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
叉树是一种常见的数据结构,它由节点组成,每个节点最多有两个子节点:左子节点和右子节点。二叉树的基本操作包括插入、删除、查找和遍历。下面我将介绍这些操作的基本实现方式: 1. 插入操作:插入节点时,需要先找到合适的位置,并创建一个新节点进行插入。如果要插入的位置已经存在节点,可以选择在左子树或右子树中插入新节点。 2. 删除操作:删除节点时,需要考虑三种情况: - 被删除节点没有子节点:直接删除该节点。 - 被删除节点只有一个子节点:将子节点替代被删除节点的位置。 - 被删除节点有两个子节点:找到被删除节点的后继节点(右子树中最小的节点),将后继节点的值复制到被删除节点,并删除后继节点。 3. 查找操作:可以使用递归或迭代方式进行查找。递归方式从根节点开始,根据比较结果选择左子树或右子树进行继续查找。迭代方式使用循环结构,通过比较节点的值来决定向左子树或右子树移动。 4. 遍历操作:有三种常见的遍历方式: - 前序遍历:先访问根节点,然后递归遍历左子树,最后递归遍历右子树。 - 中序遍历:先递归遍历左子树,然后访问根节点,最后递归遍历右子树。中序遍历可以按照从小到大的顺序访问二叉搜索树中的所有节点。 - 后序遍历:先递归遍历左子树,然后递归遍历右子树,最后访问根节点。 以上是二叉树的一些基本操作,它们在实际的数据结构算法实习中经常用到。希望对你有帮助!如果你还有其他问题,可以继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

初猿°

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

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

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

打赏作者

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

抵扣说明:

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

余额充值