树和森林的存储结构简单理解
树和森林的结构就不说了,和二叉树的区别就是每个节点可以有多个子树(子节点)。如下就为树的抽象图。
树的存储结构
从前面的文章,一般存储的方式都有两种,一种顺序,一种链式,顺序就是创建一个结构体的数组,将每个节点放入,但是缺点就是你创建数组都是指定性的创建10个单位或者几个单位,创建的多了,有空余,创建的少了又不够,链式是有一个你就连接一个,没有就算了,资源较节省,但是稍微难理解。
树的表示方法有三个:双亲,孩子,兄弟孩子。第一种是纯数组顺序类型,第二种是数组+链式,第三种是纯链式来表示。
一.双亲表示法
双亲表示法主要是用数组来(一组连续的空间)存储节点。首先知道双亲的含义,就是节点的爹,就是连接的上一个节点。那么它是如何通过数组中的节点来实现树的结构。只需要在节点的结构体里加上一个索引就可以了,指向它的父节点在数组中的位置。就实现了连接。
在数组中存放节点的位置顺序没有要求,只需要在它的结构体中的索引设置父节点的位置即可,如上图所示。
那么首先定义树节点的结构体:
typedef struct PTNode //定义单个树节点
{
ElemType data; //节点中存储的数据
int parent; //父节点在数组中的位置
}PTNode
typedef struct
{
PTNode[MAX_SIZE]; //定义一个结构体数组存放节点
int r,n; //根的位置和节点数
}PTree;
下面实现一下该结构的构造
typedef struct PTNode //定义单个树节点
{
char name[10]; //这里存储学生姓名
int parent; //父节点在数组中的位置
}PTNode
typedef struct
{
PTNode[10]; //定义一个结构体数组存放节点,这里假设存放10个
int r,n; //根的位置和节点数
}PTree;
int main()
{
PTree tree; //创建 树
tree.r=0; //设置树的根节点在数组中的位置
int n=tree.n=3; //设置树的节点数,你要创建几个就节点
for(int i=0;i<n;i++) //在数组中递归,创建节点,n表示你要创建3个节点,一般数组第一个都为根节点
{
scanf("%s",&tree.nodes[i].name); //先输入每个节点的数据
scanf("%d",&tree.nodes[i].parent); //再输入每个几点的父节点在数组中的位置,-1表示为根节点。
}
}
}
例如上面输入了三个节点,第一个为根节点,所以设置它的parent索引为 -1,然后下面两个节点的parent索引为0,指向根节点,所以就构成了简单的树
如上图所示,自然想到了找根节点的函数方法,假如给了一个节点数组,你不知道根节点在数组中的位置,定义一个找根节点的函数方法
void findbase(PTree &tree,int x) //传入树,和随便一个节点(在数组中的位置)
{
while(tree.nodes[x].parent!=-1) //因为设置的根节点的parent索引为-1,所以当其为-1的时候,说明它就是根节点
{
x=tree.nodes[x].parent; //根据树的结构可以理解,一直向上查找父节点就找到了根节点在数组中位置
}
printf("已经找到了根节点,其内容为%s",tree.nodes[x].name); //将根节点的值输出
}
然后调用该函数,随便传入参数为1,还是以上面的例子
findbase(tree,1); //查找节点为小红的父节点,因为该节点在数组中的位置为1,所以第二个参数为1
这里只是一个简单的例子,也可以创建多个节点做实验。查找根节点的方法也可以使用递归的方法来查找,可以自己做一下。
孩子表示法
以孩子为命名,所以孩子应该是该方法的主体,上面说了该方法是以数组和链表相解合的,所以和上面结构体定义类似,只是在每个单个节点里创建一个链表代替了在结构体数组中parent的作用。
假如某个节点有多个孩子,那么在该节点里面创建链表将它孩子的位置放进去即可,顶替了parent作用。(链表里只存它孩子在数组中的位置,并不存孩子的数据)。所有节点还是放在结构体数组中。如下图所示 (后面的尖表示空,其为叶子)
typedef struct TList //创建每个节点的链式表,哪个节点有孩子就创建链式表
{
int child; //孩子在数组中的位置,可以看上图
struct TList *next; //下一个节点的地址
} *TList;
typedef struct
{
ElemType elem; //每个节点中需要放的类型数据
TList firstchild; //如果有孩子,就创建,如果没有,设置其为空
}TNode;
typedef struct
{
TNode nodes[MAX_SIZE]; //创建树和上面差不多
int r,n;
}Tree;
孩子表示法就是在双亲表示法上的每个节点创建一个链式表,若有孩子就创建链式表,没有就置空,且链式表中每个节点存放的是孩子在数组中的位置,不存放孩子的数据。例如你想看某个节点有没有孩子:
node.firstchild->child;
若没有孩子则该child值为空,若有,可以再调用它的next看是否有下一个孩子
孩子兄弟表示法
顾名思义,孩子兄弟表示法,是以孩子和兄弟为中心,也被称为二叉树表示法,就是把给的一个树转化为二叉树的方式理解。首先转化如图所示:
结构体存储图如下:
如上图所示,将树转化为二叉树,那么二叉树就只能连接两个节点,两个指针域(左面child,右面sibling),一个child,一个sibling(兄弟),将其节点的第一个孩子放到child里,第二个孩子放到第一个孩子的sibling里,第三个孩子放到第二个孩子的sibling里,那么 C节点的孩子自然放到C节点的child里。那么二叉树中BCD就为兄弟,自然就是A的孩子。
所以二叉树表示法就是将树的孩子放到左面节点,兄弟就放到右节点,那么找A的第二个孩子只需要找它第一个孩子,再调用其的兄弟指针就可以找到第二个孩子的节点,依次可得找第三个,那么继续调用第二个孩子的兄弟节点即可。
结构体定义自然就为:
typedef struct Tree
{
ElemType elem;
struct Tree *child,*sibling;
}Node,*Tree;
创建二叉树函数:(这里假设每个节点的数据为char数据)
void CreateTree(Tree &T)
{
char t;
scanf("%c",&t);
getchar(); //消除回车键的影响,否则会直接进行两次scanf,导致第二次scanf失效
if(t==' ') T=NULL; //当输入为空格的时候,表示子树为空
else
{
if(!(T=(Node*)malloc(sizeof(Node)))) exit;
T->elem=t; //将数据域赋值
CreateTree(T->child); // 创建孩子节点
CreateTree(T->siblig); // 创建兄弟节点
}
}
那么该先序遍历算法为:
void PreOrder(Tree &T){
if(T){
printf("%s",T->elem);
PreOrder(T->child);
PreOrder(T->sibling);
}
}
二叉树先序遍历和树的线序遍历结果相同
树的中序和后续遍历可见下面文章:
树的遍历
最近发现了一个国外挺牛的网站,将所有数据结构都实现了可视化。你可以调节演示速度,有各种算法演示,比如什么查找之类,红黑树,什么都有。可能就是全英文,看不懂的可以用浏览器的翻译软件。地址如下: