zoukankan      html  css  js  c++  java
  • 第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(≤ 0),这是树中的节点的总数-并且因此节点编号从0到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;
    View Code

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

    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
        
    }
    View Code

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

    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<<'
    '<<"ok"<<'
    ';
                    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;
    }
    View Code

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

    #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;
    }
    View Code

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

    从开始的二叉树,普通的树,再到森林,还有最优树(哈夫曼树),难点在于如何建立这些树,这与它的存储结构密切相关,所以,分析树的存储结构尤为重要,也是树的难点,只有树建立起来,之后操作才能够更好的进行。

    ---------------------------------------------------------书上有路,学海无涯。 生活总是很忙碌,也许这才是生活真正的奥秘。--------------------------------------------------------- 作者:Charzueus 来源:博客园 本博文版权归作者所有! 禁止商业转载等用途或联系作者授权,非商业转载请注明出处! 版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
  • 相关阅读:
    linux ls
    ExtJs 弹出窗口
    Redhat5 装中文
    Linux下配置JDK以及报cannot restore segment prot after reloc: Permission denied错解决方案
    工具
    得到剪切的图片
    label button等设置不同颜色的标题
    UITableView UITextField 键盘挡住
    给出颜色生成图片
    一个不错的学习网站
  • 原文地址:https://www.cnblogs.com/chenzhenhong/p/10765525.html
Copyright © 2011-2022 走看看