第六节课:8.20:树
一、树的基本概念:
1.树的定义:
树是n(n>0)个节点的有穷集;
树是由一个根节点和若干颗子树组成;
2.树有关的概念:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2oRNsMt-1630465703415)(第六节课:8.20树.assets/image-20210820101625940.png)]
1.节点度:
一个节点拥有的子树的棵树
2.树的度:
节点度的最大值
3.叶节点:
度为0的节点
4.分支节点:
度不为0的节点
5.节点的层:
以根节点的层数定义为1,往子节点走,走一层深度+1
6.深度:
节点层的最大值
7.孩子和父亲:
一个节点和它的子树之根的关系为父子关系(一个节点和他的直接后继,该节点成为父亲节点,直接后继为孩子节点(子节点),孩子只能由一个父亲,一个父亲可以有多个孩子)
8.祖先和父亲:
一个节点的子树的任意节点成为该节点的子孙节点(父亲节点的根节点和其子节点是祖孙关系,祖孙关系是父子关系的延拓,决定了纵向次序)
9.森林:
由m(m>=0)棵互不相交的树的集合称为森林。(去掉根节点,子树之间构成森林,子树的根节点之间无联系)
10.有序无序树:
树中任意节点的子节点之间有顺序关系(兄弟之间有长幼关系)
二、树的特性:(分析特性是实现代码的前提)
1.每一个节点:有至多一个前驱结点,n(n>0)个后继节点
2.祖孙关系是父子关系的延续,决定了纵向次序
3.有序树:兄弟之间有长幼之分
4.树中只有根节点无前驱,叶节点无后继
三、代码的实现:
1.方法:
1.分析数据类型
可以是int,float,char等等;所以做成模板
template
2.分析数据的结构-用什么去实现最合适
结合树结构的特性:
树是由节点组成的,所以节点是基本元素;
而这一元素包含几个内容:
(1)要实现前驱和后继,通过指针来实现,由于子节点有多个,
方法一:所以指针也要有多个可以用vector实现
如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTcSI6NR-1630465703418)(第六节课:8.20树.assets/image-20210820111127270.png)]
方法二:用三个指针:parent,brother,child
如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k2xNLCVr-1630465703420)(第六节课:8.20树.assets/image-20210820110834266.png)]
(2)所以加上数据域和指针域可以用到我们学过的结构体做好封装成为一个节点
(3)根据特性实现增删改查即可
3.选好之后,实现增删改查
2.代码:
#pragma once
template <class T>
class My_Tree
{
struct Treenode
{
T data;
Treenode* parent;
Treenode* brother;
Treenode* child;
};
Treenode* proot;
public:
My_Tree();
~My_Tree();
void clear();
bool findval(T const& find_data);
//void insert_val(T const& val, Treenode* pos, bool child = 1);
//这里的处理非常灵活,根据树中的值,找到该值对应的位置,在相应的位置插入
void insert_val(T const& val, T const& findval, bool ischild = true);
private:
void _clear(Treenode* root);
Treenode* _findval(T const& find_data, Treenode* root)
{
//先找兄弟节点,再找子节点;
if (root)
{
if (root->data == find_data)
return root;
Treenode* tempnode = _findval(find_data, root->brother);
if (tempnode != nullptr)
{
return tempnode;
}
return _findval(find_data, root->child);
}
else
return nullptr;
}
};
//出错的原因是什么???
//template <class T>
//My_Tree<T>::Treenode * My_Tree<T>::_findval(T const& find_data, Treenode* root)
//{
//
//}
template <class T>
void My_Tree<T>::insert_val(T const& val, T const& findData, bool ischild = true)
{
//创建一个新
//Treenode* tempnode;//单单这一句话只是定义一个指针,并没有初始化,为其分配内存
Treenode* temp_insert_node = new Treenode;
temp_insert_node->data = val;
temp_insert_node->parent = nullptr;
temp_insert_node->brother = nullptr;
temp_insert_node->child = nullptr;
if (proot == nullptr)
{
proot = temp_insert_node;
}
else
{
//位置插入错误判断
Treenode* pos = _findval(findData, proot);
if (pos)
{
if (true == ischild)//插入作为子节点
{
if (pos->child)//有子节点
{
//有序从最后插入
Treenode* tempnode1 = pos->child;
while (tempnode1->brother) tempnode1 = tempnode1->brother;
tempnode1->brother = temp_insert_node;
temp_insert_node->parent= pos;
}
else//无子节点
{
pos ->child = temp_insert_node;
temp_insert_node->parent = pos;
}
}
else if (ischild != 1)//插入作为兄弟节点(有序为例)
{
//有序直接从最后插入
while (pos->brother) pos = pos->brother;//往后遍历到最后一个兄弟
pos->brother = temp_insert_node;
temp_insert_node->parent = pos->parent;
//无序:断开链接从中间插入
/*temp_insert_node->brother = pos->brother;
pos->brother = temp_insert_node;
temp_insert_node->parent = pos->parent;*/
}
}
else
{
throw"insert error!";
}
}
}
template <class T>
bool My_Tree<T>::findval(T const& find_data)
{
if (_findval(find_data, proot) != nullptr)
return 1;
else
return 0;
}
template <class T>
void My_Tree<T>::_clear(Treenode* root)
{
//递归删除
if (root)
{
_clear(root->brother);
_clear(root->child);
delete root;
root = nullptr;//单只写这一步会造成内存泄漏
}
else
return;
}
template <class T>
void My_Tree<T>::clear()
{
_clear(proot);//这个函数看不到改不了
}
template <class T>
My_Tree<T>::My_Tree()
{
//增加节点的时候来加入头节点
proot = nullptr;
}
template <class T>
My_Tree<T>::~My_Tree()
{
clear();
}
3.遇到问题:clear函数调用后,程序抛出异常;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-giYQ6Hgv-1630465703423)(第六节课:8.20:树.assets/image-20210901104754775.png)]
(1):分析问题:
结果调试后,在clear调用之后,程序在main函数return前,调用析构再次调用_clear()函数时候,出现异常
(2):定位问题的原因:
最后对根节点的删除操作的时候,虽然删除了proot的内容,但是proot没有置空;
传进来的是proot的拷贝root,root赋值为空,但是proot没有
(3):改正后调试成功:
template < class T>
void CMytree<T>::_clear(TreeNode** Root)//这里的删除函数有点问题:
{
if (*Root)
{
_clear(&(*Root)->brother);//兄弟节点
_clear(&(*Root)->child);//子节点
delete (*Root);
(*Root) = nullptr;
}
}
4.难点解析:递归的流程:萝卜老师我用自己的这种方法画图更加好理解
复习的时候,按照这个流程再理一遍递归的思路即可;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnkHMey0-1630465703424)(第六节课:8.20树.assets/4ACE4E963E901D0F67992344EA9113B6.png)]
四、除了学习上课的知识点之外,还收获了什么?
1.一种学习方法:上课的时候,一定要带上纸和笔,把边听,边把整节课的知识点的流程框架写下来做好笔记
课后复习时:先对照知识点框架,回忆,看看自己上节课学会了什么把学会了的写下来;不会的之后看录播的时候带着问题去看,理解更加深刻,并且之后完善自己的知识点框架,做好笔记和总结;