您的位置:首页 > 其它

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

2019-04-26 21:45 127 查看

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

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

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

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

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

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

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: