第5章 树与二叉树学习小结

  前几章学习的基本都是线性的数据结构,就有顺序存储结构和链式存储结构,而这一章“树”结构是一类非线性数据结构,跟之前就有不同的点,但是,树的存储结构还是可以通过找到元素之间逻辑关系,采用类似线性表的方式,按照结点之间的逻辑关系放到线性存储中。

  这部分主要学习到二叉树的内容,二叉树有好几个性质,我想这些性质很重要,有时候在解决问题,它能够帮助理解这棵树比较抽象的结构层次,这是我在理解代码时候体会到的。二叉树存储结构跟遍历有很大的关系,遍历的结果是将非线性结构的树中结点排成一个线性序列。

这是二叉链表的存储表示:

typedef struct Bitnode
{
  int data;//结点数据域
  struct Bitnode *lchild,*rchild;//左右孩子指针
}Bitnode,*Bitree;

跟之前差不多一样,然后就最初始的建立二叉链表,用了递归方式进行创建和遍历,递归只用几行代码就把事情搞定,看起来很简单,老师很详细的讲解了递归的来龙去脉,让我们不只是停留在表面,而是要“知其然,更要知其所以然”,我想这样理解也更加深刻。二叉树另外一种存储方式-用结构体数组答题时候用的多,如下:

做PTA上题目时候,总能发现自己算法和编程的不足之处,有时需要优化改进。这次平台上的编程题目让我收获许多,其中发现它们都有相同的点,就是寻找一棵二叉树的根结点,也是后续完成各功能的关键。这里我记录一下解答Leaves List题目的过程:

7-1 List Leaves (30 分)

Given a tree, you are supposed to list all the leaves in the order of top down, and left to right.

Input Specification:

每个输入文件包含一个测试用例。对于每种情况,第一行给出正整数N(≤ 1 0),这是树中的节点的总数-并且因此节点编号从0到N - 1。然后随后是N行,每行对应一个节点,并给出节点左右子节点的索引。如果孩子不存在,将在该位置放置“ - ”。任何一对孩子都被一个空间隔开。

Output Specification:

对于每个测试用例,按照自上而下和从左到右的顺序在一行中打印所有叶子的索引。任何相邻数字之间必须只有一个空格,并且该行末尾没有额外的空格。

样本输入:

8
1 -
- -
0 -
2 7
- -
- -
5 -
4 6

样本输出:

4 1 5跟之前有结点名字不同,行序号作为结点名,在一行输入其左右孩子,节省了存储名字的空间,直接用数组下标记录。用上面说到的结构,将一颗非线性二叉树存放到一个结构体数组中,因为根据题目内容特点,这样比较二叉链表好实现。

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

typedef struct //定义结构体数组,存放结点左右孩子
{
    int Left;
    int Right;
}Node;

下面这一步,感觉几个题目都用到了,在读取数据的同时,找到树的根结点:

int BuildTree(Node T[]) //建立二叉树
{
    int i,N;
    bool check[100]={false};//check数组用于查找树的根节点
    char x,y;
    cin>>N;

    if(N)//树结点个数不为0 ,之前没有这一步,虽然能够通过,但是程序不够严谨
    {
        for(i = 0; i < N; ++i)
        {
            cin>>x>>y; 

            if(x != ‘-‘)//若结点不为空,将节点索引放入左子树结点
            {
                T[i].Left = x - ‘0‘;
                check[T[i].Left] = true;//记录此结点索引,在check数组将该位置置为true
            }
            else
            {
                T[i].Left = -1;//若结点为空,将其置为-1
            }

            if(y != ‘-‘)//同上,放入右子树
            {
                T[i].Right = y - ‘0‘;
                check[T[i].Right] = true;
            }
            else
            {
                T[i].Right = -1;
            }
        }
        for(i = 0 ; i < N; ++i)//遍历check数组,除了根结点之外,其它元素为true或-1
        {
            if(!check[i]) return i;//返回根结点下标
        }
    }
    else return -1;// 若树为空,返回 -1

}

接下来,题目要求是输出叶结点,所以用了队列的特性,在出入队进行操作,第一次,我的代码是这样的:

void Leafnode(Node T[],int k){
    queue<int> q;
    int flag = 1;
    if(k == -1) return;//若树为空,返回
    q.push(k);//将根结点下标入队
    int temp;
    while(!q.empty())
   {
        temp = q.front();//取队头元素
        q.pop();//队头元素出队
        if((T[temp].Left == -1) && (T[temp].Right == -1))//当此结点左右孩子都为空时,说明它是叶结点,输出
      {
            if(flag)//用flag判断是否为要输出空格
         {
                cout<<temp;
                flag = 0;
         }
            else
                cout<<" "<<temp;
      }
        if(T[temp].Left != -1)//左结点不为空时,入队
            q.push(T[temp].Left);
        if(T[temp].Right != -1)//右结点不为空时,入队
            q.push(T[temp].Right);
   }
}

然后我看到最后面2个if语句,感觉可以简化一下,跟前面if合在一起判断,因为其实进来一个结点,可以先判断它是否为空,空则不进入语句,非空则执行if语句,就不用单独判断结点左和右孩子是否为空再入队,于是我修改代码如下:

void Leafnode(Node T[],int k)
{
    queue<int> q;
    int flag = 1;
    if(k == -1) return;//若树为空,返回
    q.push(k);//将根结点下标入队
    int temp;
    while(!q.empty())
    {
        temp = q.front();//取队头元素
        q.pop();//队头元素出队 

        if(temp!=-1)//叶结点不为空时,执行以下语句
        {
                if((T[temp].Left == -1) && (T[temp].Right == -1))//若该结点的左右结点为空,输入叶结点
            {
                //cout<<‘\n‘<<"ok"<<‘\n‘;
                if(flag)//flag判断是否为第一个输出
                {
                cout<<temp;//第一个输出前面不带空格
                flag = 0;//同时将flag置为0
                }
                else
                    cout<<" "<<temp;//输出元素前面带空格 

            }
            q.push(T[temp].Left);//此结点的左右孩子入队
            q.push(T[temp].Right);
        }//执行下一轮循环
    }
}

主函数:

int main()
{
    Node t[100];
    int k;
    k = BuildTree(t);
    Leafnode(t,k);//cout<<"ok";
    return 0;
}

另外,老师教打天梯赛《深入虎穴》那道题目,从开始分析,逻辑思维引入,一步步优化算法的数据结构,最后把控全局的框架思想,真让我意识到自己分析能力不够强大,还需努力了。那节课结束之后,我和几个同学都在很小的细节出错,因为题目有很多for循环语句,主要问题出现在起始下标,还有循环次数,这才感到编程先顾及全局,然后要注意每一部细节。仔细修正之后,最后成功解决。不过,我对老师开始提到的用单链表实现方式有点兴趣探究一下,所以之后尝试用单链表存储每个门之后通道序号,最后在Dev运行正确,但是提交之后只通过了3个测试点,另外3个不知道错在哪里。

#include<iostream>
#include<queue>
#include<list>
using namespace std;

typedef struct
{
    int doors;//门的数量
    list<int> l; //存放每个门后面通向门序号的单链表
}node;

int input(node *a,int n)//读入n扇门的信息 ,并返回跟所在 门序号(下标)
{
    int i,j,value;
    bool *vi;
    vi=new bool[n+1];

    for(i=0;i<n+1;i++)
        vi[i]=false;

    for(i=1;i<n+1;i++)
    {
        cin>>a[i].doors;
        if(a[i].doors)//门后面有通道
        {
            for(j=0;j<a[i].doors;j++)
            {
                cin>>value;
                a->l.push_back(value);//将门序号存储到单链表中
                vi[value]=true;
            }
        }

    }
    for(i=1;i<n+1;i++)//找出根结点所在下标(起点)
    {
        if(!vi[i]) return i;
    }
}

int level(node *a,int r)//从a[r]开始对a数组进行层次遍历,并返回遍历最后一个结点的序号
{
    queue<int> q;
    int f,i,t;
    q.push(r);

    while(!q.empty())
    {
        f=q.front();
        q.pop();

        if(a[f].doors) //t号门后面有通道门
        {
            for(i=0;i<a[f].doors;i++)
            {
                //cout<<t<<" ";
                t=a->l.front();//取链表头元素
                a->l.pop_front();//去掉链表头元素
                q.push(t);// 链表头元素入队
            }
        }
    }
    return f;
}

int main()
{
    node *a;//用于存储整棵树
    int n,root;
    cin>>n;
    a=new node[n+1];
    root=input(a,n);
    cout<<level(a,root);
    return 0;
}

虽然没能成功,但这个过程理解了许多,这个星期做的题目比之前多了,编程打代码感觉挺好,就是还缺乏分析能力和逻辑转化为现实操作,希望之后的学习能够提高这方面缺陷。

原文地址:https://www.cnblogs.com/chenzhenhong/p/10765525.html

时间: 2024-10-10 05:08:09

第5章 树与二叉树学习小结的相关文章

数据结构 第5章 树的二叉树 单元小结(2)遍历二叉树和线索二叉树

概念: 遍历二叉树: 遍历:指按某条搜索路线遍访每个结点且不重复(又称周游). 遍历的用途:它是树结构插入.删除.修改.查找和排序运算的前提,是二叉树一切运算的基础和核心. 时间效率: O(n) //每个结点最多访问两次 空间效率: O(n) //栈占用的最大辅助空间 用栈进行迭代运算 和队列很像 先序: 中序: void PreOrderlteration(BiTree T) void InOrderIteration(BiTree T) { stack<BiTree> s; stack&l

数据结构学习之第7章 树和二叉树

数据结构学习之第7章 树和二叉树 0x7.1.1 树的基本概念 ?1.树的定义 ? 树是由n(n>=0)个结点(或元素)组成的有限集合(记为T) ? 如果n>0,这n个结点中有且仅有一个结点作为树的根结点,简称为根,其余结点可分为m(m>=0)个互不相交的有限集\[T_{1}T_{2}\cdots T_{m}\],其中每个子集又是一棵符合定义的子树,称为根结点的子树. 知识点:由树的定义我们可以看出来树的结构是递归的 ?2.树的逻辑表示法 ? 1.树形表示法 ? 2.文氏图表示法 ? 3

第五章 树和二叉树

上章回顾 单链表的基本操作,包括插入.删除以及查找 双向链表和循环链表的区别 [email protected]:Kevin-Dfg/Data-Structures-and-Algorithm-Analysis-in-C.git 第五章 第五章 树和二叉树 树和二叉树 [email protected]:Kevin-Dfg/Data-Structures-and-Algorithm-Analysis-in-C.git 预习检查 什么是二叉树 树的遍历有哪几种方式 树有哪些应用 [email pr

数据结构-王道2017-第4章 树与二叉树-树、森林

1.树的存储结构有多种,既可以采用顺序存储结构,也可以采用链式存储结构,都要求能唯一地反映出树中各结点之间的逻辑关系,三种常用的存储结构 1)双亲表示法 采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲节点在数组中的位置,根节点下标为0,其伪指针域为-1. #define MAX_TREE_SIZE 100 //树中最多结点数 typedef struct{ //树的结点定义 ElemType data; //数据元素 int parent; //双亲位置域 }PTNo

数据结构期末复习第六章树和二叉树

知识点: 先序遍历二叉树规则:根-左-右 1.访问根结点 2.先序遍历左子树 3.先序遍历右子树 中序遍历二叉树规则:左-根-右 1.先中序遍历左子树 2.再访问根节点 3.最后访问中序遍历右子树 后序遍历二叉树规则:左-右-根 1.后序遍历左子树 2.后序遍历右子树 3.访问根结点 1.  一棵二叉树的先序遍历结果为ABCDEF,中序遍历结果为CBAEDF,则后序遍历结果为(A)A. CBEFDA                       B. FEDCBAC. CBEDFA        

数据结构——第三章树和二叉树:03树和森林

1.树的三种存储结构: (1)双亲表示法: #define MAX_TREE_SIZE 100 结点结构: typedef struct PTNode { Elem data; int parent; //双亲位置域 } PTNode; (2)孩子双亲链表表示法: typedef struct PTNode { Elem data; int parent; //双亲位置域 struct CTNode* nextchild; } *ChildPtr; (3)树的二叉链表(孩子-兄弟)存储表示法:

第五章 树与二叉树总结

树结构是一类重要的非线性数据结构 1.树的定义:树(Tree)是n(n>=0)个结点的有限集,它或为空树(n=0):或为非空树: 对于非空树: (1)有且仅有一个称之为根的结点: (2)除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1,T2,...,Tm,其中每个集合本身又是一颗树,并且称为根的子树(SubTree): 2.树的基本术语 (1)结点:树中的一个独立单元. (2)结点的度:结点拥有的子树数称为结点的度. (3)树的度:树的度是树内各结点的最大值. (4)叶子:度为0

数据结构-王道2017-第4章 树与二叉树-二叉树的遍历

typedef int ElemType; typedef struct BitNode { ElemType data; //数据域 struct BitNode *lchild, *rchild; //左右孩子指针 }BitNode,*BitTree; void visit(BitNode *b) { printf("%d ", b->data); } //无论采用哪种遍历方法,时间复杂度都是O(n),因为每个结点都访问一次且仅访问一次,递归工作栈的栈深恰好为树的深度,空间复

数据结构——第五章 树与二叉树

树是一对多的结构 结点:树的小圆圈 度:结点有多少个分叉 叶子结点:结点的度为0 双亲:parent 孩子:child 二叉树:树的度不超过2 满二叉树:每一层都是满的 完全二叉树:除了最后一层都是满的,最后一层左边都是齐全连续的. 性质1:对一颗二叉树,第i层最多有2的i-1次方个 性质2:对一颗二叉树,最多有2的i次方-1个 性质3:n0=n2+1 n0+n1+n2=n(n0--结点度为0的个数,n2--结点度为2的个数) 性质4:具有n个结点的完全二叉树深度为 math.floor(log