第三讲 树

树是一种非常重要,并且对于我来说较难的数据结构,此博客为我参考《大话数据结构》与慕课网浙大数据结构网课所做的学习笔记,如有错误,欢迎指正。
2020.2.19草稿改动,添加树的基本概念。
2020.3.11改动,添加洛谷例题(超简洁已知中后序求前序遍历)。

树基本概念

树的定义:树是有多个结点的有限集合。结点数为0时为空树。在任何一棵非空树中;(1)有且只有一个特定的称为根的结点;(2)当节点数大于1时,其余结点可分为多个互不相交的子树。
这是比较专业的说法,在我看来哈,就是说你在一棵树中,只有一个根节点。但是吧,你可以把树拆成多个不相交的子树,而每个子树又有子树的根节点。而后面的遍历二叉树的关键,便是需要理解树的定义,递归建树。
树的存储结构:双亲表示法,孩子表示法,孩子兄弟表示法。
在之后的图中,有大量运用到三种表示法的地方。
双亲表示法:

dataparent

一个数据域,一个指向双亲结点的指针域。
孩子表示法:

datachild1chid2child3childn

第一种呢,是这样的,因为我们不知道具体有多少个子结点,所以这样保存数据域与指针域。但问题很明显,这样子会浪费大量空间。
2.

datadegreechild1child2childn

第二种,我们添加了一个degree域用来存储双亲结点的子结点个数,节省了空间。但是因为各个结点的结构不同,会带来时间上的损耗。
3. 以上两种都拥有很明显的缺陷,而最经常用的便是第三种。
具体结构:将每个结点的孩子结点排列起来,用链表串接,则n个结点有n个孩子链表,若为叶结点则孩子链表为空,最后n个结点用数组存储起来。
孩子链表的结构

childnext

表头数组的表头结点

datafirstchild

这种结构最为常用,在之后的图中,便有一种表现图的方式与其有关。

孩子兄弟表示法
这种表示法最大的好处便是将一棵树转化为了二叉树,下面是他的结构。

datafirstchildrightsib

firstchild域指的是该结点的第一个孩子结点的地址,rightsib 域指的是该结点最右边孩子结点的地址。

二叉树

二叉树是树这一种数据结构中的重头戏,二叉树的定义简而言之便是:
一棵树它的所有节点至多只有两个子节点。
而二叉树可以使用顺序存储,但是我们一般用链式存储较多,接下来便是我自己写的建立一个二叉树的代码。

void creat(tree *&root)
{
    char t;
    scanf("%c",&t);
    if(t=='#')
    {
        root=NULL;
    }
    else
    {
        root=(tree *)malloc(sizeof(tree));
        root->data=t;
        creat(root->l);
        creat(root->r);
    }
}

在我们建立了二叉树之后,我们便需要对他进行使用了,而使用二叉树的基本前提便是二叉树的遍历。二叉树的遍历方法有四种:前序遍历,中序遍历,后序遍历,层次遍历。
关于前中后序,我认为主要需要理解树是一个递归建立的数据结构,这样便比较好理解树的前中后序的遍历。
这里贴一个我写的层次遍历的函数,之后题目会有已知中后序求前序的代码。

**层次遍历:**层次遍历的思想为不断的将每一层的根节点存入队列中,然后输出队头再出队,最后将根节点的左右节点若不为空就入队。
如此即可。

void levels_showtree(tree *root)
{
    if(root==NULL)
        return;
    queue <tree *> q;
    q.push(root);
    while(!q.empty())
    {
        tree *t=q.front();
        cout<<t->data;
        q.pop();
        if(t->l!=NULL)
        {
            q.push(t->l);
        }
        if(t->r!=NULL)
        {
            q.push(t->r);
        }
    }
    cout<<endl;
}

而前序、中序、后序遍历的算法十分类似。核心算法就三个语句。

void 递归(BiTree T)
{
	if(T==NULL)
		return;
	//下面是操作
	操作;
	递归(T->lchild);
	递归(T->rchild);
}

这是一个前序遍历的伪代码,前序遍历的顺序为:根左右;所以我们先操作,在递归左,在递归右。中序遍历的顺序为:左根右;这样的话,我们需要先递归左节点,在操作,再递归右节点。而后序遍历顺序为:左右根;也是同样的操作。

树要理解起来不难,但是理解的关键点在于需要记住它的定义是一个递归的定义,树中递归是基础。

已知中后序求前序

本来我自己写的时候,是一个十分复杂的代码,最近写洛谷,发现了一种格外简洁的代码,贴在这里分享。

题目:

在这里插入图片描述

#include <cstdio>
#include <iostream>
using namespace std;
void print(string t1,string t2)
{
    if(t1.size()>0)
    {
        char ch=t2[t2.size()-1];
        cout<<ch;
        int k=t1.find(ch);//把根节点在中序中的位置找出
        print(t1.substr(0,k),t2.substr(0,k));
        print(t1.substr(k+1),t2.substr(k,t2.size()-k-1));//递归求解
    }
}
int main()
{
    std::ios::sync_with_stdio(false);//加快cin速度
    string t1,t2;
    cin>>t1;
    cin>>t2;
    print(t1,t2);
}

这里主要运用到了一个substr函数,这个函数第一个参数是string的数据,第二个参数是传递一个第一个参数的子字符串,如此我们可以大大改进代码长度,便于理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值