前几章学习的基本都是线性的数据结构,就有顺序存储结构和链式存储结构,而这一章“树”结构是一类非线性数据结构,跟之前就有不同的点,但是,树的存储结构还是可以通过找到元素之间逻辑关系,采用类似线性表的方式,按照结点之间的逻辑关系放到线性存储中。
这部分主要学习到二叉树的内容,二叉树有好几个性质,我想这些性质很重要,有时候在解决问题,它能够帮助理解这棵树比较抽象的结构层次,这是我在理解代码时候体会到的。二叉树存储结构跟遍历有很大的关系,遍历的结果是将非线性结构的树中结点排成一个线性序列。
这是二叉链表的存储表示:
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