二叉树的构建、遍历、转广义表等基本操作

二叉树的构建、遍历、转广义表等基本操作

一 结构定义

  二叉树我们可以分成两个部分:结点和边,这一点和图是一样的,其中结点代表事件,那么边就代表着事件之间的关系
  此外,每一个结点我们都可以看成其左右子结点的并集;换句话说,一个结点的左右孩子结点取并集,那么并集就可以代表这个结点;因此我们不难得到:树 这种数据结构对应的是一系列完全包含关系 (这一点很重要)
  此外之前我们说过有一种数据结构适合处理具有完全包含关系的问题,这种数据结构是什么呢?那就是栈!!!
 
  话不多说,我们首先实现二叉树的结构定义

typedef struct Node {	//包含数据域和左右孩子两个指针域
    char data;
    struct Node *lchild, *rchild;
} Node;
二 二叉树的构建
  1. 我们知道二叉树有前、中、后三种遍历顺序,而在二叉树的前、中、后三种遍历顺序中,已知中序遍历 + 前序遍历,或者是中序遍历 + 后序遍历,就可以唯一的确定一棵二叉树(前序 + 后序 不可以),因此给定我们两种序列搭配中的任意一种,我们就可以构建我们想要的二叉树。
    实现代码如下:
Node *getNewNode(char val) {	//根据传入的值,生成一个二叉树结点
    Node *node = (Node *)malloc(sizeof(Node));
    node->data = val;
    node->lchild = node->rchild = NULL;
    return node;
}

//参数:先序和中序遍历序列的字符串,以及字符串的长度len
//返回值:返回构建好的二叉树的根结点
Node *build(char *pre_str, char *in_str, int len) {
    Node *root = getNewNode(pre_str[0]);	//创建根结点
    int ind = strchr(in_str, root->data) - in_str; //获取根结点字符在中序遍历序列中的下标ind
    if (ind > 0) {	//若左子树非空,递归建设左子树
        root->lchild = build(pre_str + 1, in_str, ind);
    }
    if (len - ind - 1 > 0) {	//若右子树非空,递归建设右子树
        root->rchild = build(pre_str + ind + 1, in_str + ind + 1, len - ind - 1);
    }
    return root;
}
  1. 除使用前序 + 中序和后序 + 中序 来构建一颗二叉树之外,我们通常还会根据二叉树对应的广义表来构建一棵二叉树(每一个广义表都唯一的对应一棵二叉树),关于何谓广义表,请看官向下看:
    广义表
    如图:上面的这棵二叉树有右侧这四种广义表表示形式,笔者本人更喜欢第二种形式,接下来我们就代码实现:
//参数:广义表对应的字符数组,node_num为传入参数,用于得到树中结点的个数
//返回值:返回构建好的二叉树的根结点
Node *build(char *str, int *node_num) {  
    Node *tmp = NULL, *top_p = NULL;
    int flag = 0;
    stack<Node *> s;
    while (str[0]) {  //遍历广义表字符串
        switch (str[0]) {
            case '(': {  //遇到左括号
                flag = 0;
                s.push(tmp);
                tmp = NULL;
                break;
            }
            case ')': {  //遇到右括号
                top_p = s.top();
                s.pop();
                break;
            }
            case ',': {  //遇到逗号
                flag = 1;
                tmp = NULL;
                break;
            }
            case ' ': {  //遇到空格
                break;
            }
            default: {  //遇到字母
                tmp = getNewNode(str[0]);  //创建结点
                if (!s.empty() && !flag) {
                    s.top()->lchild = tmp;
                } else if (!s.empty() && flag) {
                    s.top()->rchild = tmp;
                }
                (*node_num)++;  //结点数加一
            }
        }
        str++;  //str指针后移
    }
    if (tmp && !top_p) top_p = tmp;
    return top_p;
}

  想必各位看官看到代码,是一头雾水(小编当初也是),不着急,我们先从 括号匹配 说起
  说到括号匹配 (若不了解可上网搜索) 这道题,想必大家都马上想到了相应的解决办法——
  具体方法为:当我们遇到左括号时,将括号入栈,遇到右括号时,查看栈顶元素,看是否为相应的左括号,若不匹配,直接返回false,说明整体括号不匹配;若匹配,弹出栈顶元素,接着遍历字符串,当遍历完字符串时,若此时栈不为空,那么返回false,若栈为空,返回true,表示括号匹配。

  在这里我们把左括号看成事件的开始,把右括号看成事件的结束,而括号之外又有括号,这不就是一个事件完全包含问题吗?括号匹配是一个事件完全包含问题,同样我们在文章的开头说过,二叉树中父结点和子结点之间的关系是一个并集的关系,这个父结点同时可能还是其他结点的子结点,因此二叉树的构建同样一个事件完全包含关系的问题,我们也可以用栈来实现!!!

  最开始遇到一个字符,我们将它入栈;之后遇见左括号,我们知道接下来的字符是上一个字符的左孩子(为了判断下一个字符是栈顶元素的左孩子还是右孩子,我们设置一个标志为flag,flag = 0表示左孩子,flag = 1表示右孩子),所以我们将flag设为0,同时将之前的字符入栈;遇到左孩子对应的字符,我们生成结点,同时将栈顶元素的左孩子指针指向该结点;遇到逗号,我们知道接下来的字符就是右孩子,因此我们将flag置为1;当遇到空格时,我们不需要处理 (这里之所以判断空格,是为了防止用户输入空格),如此循化,最后我们就可以得到广义表相应的二叉树。

三 二叉树前中后以及深度优先四种遍历方式

前中后三种顺序都是相对于根结点的顺序而言的,相信大家对于这三种遍历方式都很了解,此处小编就不再赘述,直接展示代码。此外小编还想啰嗦一句,中序遍历可以把树中的结点看成一颗颗的果实,直接进行自由落体,即可得到中序序列;关于宽度优先遍历,此时需要借助队列这样的数据结构实现。

void pre_order(Node *root, vector<char>& v) {  //前序遍历,将结果保存在向量v中
    if (!root) return ;
    v.push_back(root->data);
    pre_order(root->lchild, v);
    pre_order(root->rchild, v);
}
void in_order(Node *root, vector<char>& v) {  //中序遍历
    if (!root) return ;
    in_order(root->lchild, v);
    v.push_back(root->data);
    in_order(root->rchild, v);
}
void post_order(Node *root, vector<char>& v) {  //后序遍历
    if (!root) return ;
    post_order(root->lchild, v);
    post_order(root->rchild, v);
    v.push_back(root->data);
}

void BFS(Node *root, vector<char>& v) {  //深度优先遍历,需要队列
    if (!root) return ;
    queue<Node *> q;
    q.push(root);
    while (!q.empty()) {
        Node *tmp = q.front();
        v.push_back(tmp->data);
        q.pop();
        if (tmp->lchild) q.push(tmp->lchild);
        if (tmp->rchild) q.push(tmp->rchild);
    }
}

四 二叉树转广义表

同样的简单,直接展示代码如下:

void tree_to_table(Node *root) {
    if (!root) return ;
    printf("%c", root->data);
    if (root->lchild == NULL && root->rchild == NULL) return ;
    printf("(");
    tree_to_table(root->lchild);
    printf(",");
    tree_to_table(root->rchild);
    printf(")");
}

在这里小编想插一句话,那就是在树中,绝大部分的代码实现都是通过递归实现的,因此如果递归没有学到家,那树这一块,将会十分的难熬(小编也是。。。)

五 其他操作

int get_node_num(Node *root) {  //获取树中结点数量
    if (!root) return 0;
    return get_node_num(root->lchild) + get_node_num(root->rchild) + 1;
}
int get_tree_depth(Node *root) {  //获取树的高度
    if (!root) return 0;
    int n = 0, m = 0;
    m = get_tree_depth(root->rchild);
    return max(n, m) + 1;
}
int get_leaf_num(Node *root) {  //获取叶子结点的数量
    if (!root) return 0;
    if (root->lchild == NULL && root->rchild == NULL) return 1;
    return get_leaf_num(root->lchild) + get_leaf_num(root->rchild);
}
void printAllPath(Node *T,char path[],int pathDepth){  //打印所有叶子结点到根节点的路径
    if(T != NULL){
        path[pathDepth] = T->data;
        if(T->lchild == NULL && T->rchild == NULL){
            for(int i = pathDepth; i >= 0; i--) cout << path[i] << " ";
            cout << endl;
        }else{
            printAllPath(T->lchild,path,pathDepth + 1);
            printAllPath(T->rchild,path,pathDepth + 1);
        }
    }
}

最后,其实关于二叉树的几点性质,小编就不想在这里过多的阐述了,大家直接上网搜索就可以,只有一点,小编觉得很重要,这里有必要和大家说明一下:
二叉树中国版二叉树国际版
在大学的数据结构课程当中,关于完全二叉树和满二叉树的定义都是采用了中国版的定义,但是对于想出国的小伙伴,就必须了解完美二叉树,满二叉树,和完美二叉树之间的区别以及他们的准确定义。

最后:总代码献上:

#include <iostream>
#include <stack>
#include <vector>
#include <queue>
using namespace std;

typedef struct Node {
    char data;
    struct Node *lchild, *rchild;
} Node;

Node *getNewNode(char val) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->data = val;
    node->lchild = node->rchild = NULL;
    return node;
}


Node *build(char *str, int *node_num) {  //根据字符串建树,返回根结点
    Node *tmp = NULL, *top_p = NULL;
    int flag = 0;
    stack<Node *> s;
    while (str[0]) {
        switch (str[0]) {
            case '(': {
                flag = 0;
                s.push(tmp);
                tmp = NULL;
                break;
            }
            case ')': {
                top_p = s.top();
                s.pop();
                break;
            }
            case ',': {
                flag = 1;
                tmp = NULL;
                break;
            }
            case ' ': {
                break;
            }
            default: {
                tmp = getNewNode(str[0]);
                if (!s.empty() && !flag) {
                    s.top()->lchild = tmp;
                } else if (!s.empty() && flag) {
                    s.top()->rchild = tmp;
                }
                (*node_num)++;
            }
        }
        str++;
    }
    if (tmp && !top_p) top_p = tmp;
    return top_p;
}

void in_order(Node *root, vector<char>& v) {
    if (!root) return ;
    in_order(root->lchild, v);
    v.push_back(root->data);
    in_order(root->rchild, v);
}

void BFS(Node *root, vector<char>& v) {
    if (!root) return ;
    queue<Node *> q;
    q.push(root);
    while (!q.empty()) {
        Node *tmp = q.front();
        v.push_back(tmp->data);
        q.pop();
        if (tmp->lchild) q.push(tmp->lchild);
        if (tmp->rchild) q.push(tmp->rchild);
    }
}

void tree_to_table(Node *root) {
    if (!root) return ;
    printf("%c", root->data);
    if (root->lchild == NULL && root->rchild == NULL) return ;
    printf("(");
    tree_to_table(root->lchild);
    printf(",");
    tree_to_table(root->rchild);
    printf(")");
}

int get_node_num(Node *root) {
    if (!root) return 0;
    return get_node_num(root->lchild) + get_node_num(root->rchild) + 1;
}
int get_tree_depth(Node *root) {
    if (!root) return 0;
    int n = 0, m = 0;
    m = get_tree_depth(root->rchild);
    return max(n, m) + 1;
}
int get_leaf_num(Node *root) {
    if (!root) return 0;
    if (root->lchild == NULL && root->rchild == NULL) return 1;
    return get_leaf_num(root->lchild) + get_leaf_num(root->rchild);
}


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

int main() {
    char str[100] = {0};
    scanf("%[^\n]s", str);
    int node_num = 0;
    Node *root = build(str, &node_num);
    printf("node_num = %d\n", get_node_num(root));

    vector<char> v;
    in_order(root, v);
    vector<char>::iterator it;
    printf("in_order: [");
    for (it = v.begin(); it != v.end(); it++) {
        if (it != v.begin()) printf(" ");
        printf("%c", *it);
    }
    printf("]\n");
    
    printf("tree_to_table: ");
    tree_to_table(root);
    printf("\n");

    printf("tree's depth = %d\n", get_tree_depth(root));
    printf("tree's leaf node number = %d\n", get_leaf_num(root));

    vector<char> v2;
    BFS(root, v2);
    vector<char>::iterator it2;
    printf("BFS: [");
    for (it2 = v2.begin(); it2 != v2.end(); it2++) {
        if (it2 != v2.begin()) printf(" ");
        printf("%c", *it2);
    }
    printf("]\n");

    return 0;
}
/*
输入:
A(B(D,E),C(F,G))
输出:
node_num = 7
in_order: [D B E A F C G]
tree_to_table: A(B(D,E),C(F,G))
tree's depth = 3
tree's leaf node number = 4
BFS: [A B C D E F G]
*/
题外话

加油! 路漫漫其修远兮,吾将上下而求索
树是很重要的一个部分,加油,加油递归!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值