typedef struct Node{
char data;
int parent;
}PTNode;
typedef struct{
PTNode nodes[100];
int n;
}PTree;
3. 树的双亲表示法【优点】对于实现求双亲操作很方便,时间复杂度为O(1),但【缺点】①对于求某结点的孩子结点的操作,则需要询整个数组。②另外,这种存储方式不能直接反映各兄弟结点之间的关系,所以实现求兄弟的操作也比较困难(若要找结点的孩子或者兄弟,要遍历整个树)。
4. 实际上,如果需要实现这些操作,只需将上述存储结构稍加改进:给每个数组元素增加两个域,一个域存储该数组元素所表示的结点的第一个孩子结点在数组中的序号,另一个域存储该结点的右兄弟结点在数组中的序号。在这种改进的存储结构下,就能够较方便地实现树的各种基本操作。
(1)改进一:方便获取孩子结点
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode //结点结构
{
TElemType data; //结点数据
int parent; //双亲位置
int child1; //孩子结点1
int child2; //孩子结点2
int child3; //孩子结点3
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}PTree;
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode //结点结构
{
TElemType data; //结点数据
int parent; //双亲位置
int firstchild; //长子域
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}PTree;
(2)改进二:方便获取各兄弟之间的关系
我们只需要增加一个有兄弟域,即可依次获取所有的兄弟结点。
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode //结点结构
{
TElemType data; //结点数据
int parent; //双亲位置
int rightsib; //右兄弟结点
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}PTree;
以上改进参考:https://www.cnblogs.com/ssyfj/p/9459887.html
5. 小结
存储结构的设计是一个十分灵活的过程。
若是我们既关注孩子又关注兄弟,而且对时间遍历要求高,那么我们可以扩展上面结构含有双亲域,长子域,右兄弟域。
//根据树的度来设置孩子域的个数,例如本例中度为3,设置3个孩子域
/*树的孩子表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode //结点结构
{
TElemType data; //结点数据
int child1; //孩子1结点
int child2; //孩子2结点
int child3; //孩子3结点
}PTNode;
typedef struct //树结构
{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}PTree;
【缺点】 占用了大量不必要的孩子域空指针,以上例为标准:需要3n个指针域,实际上有用n-1个(除了根节点,其他n-1个都向上需要一条边),则有2n+1个无用,浪费。
3)改进一:为每个结点添加一个结点度域,方便控制指针域的个数
【缺点】维护困难,不易实现
改进二:结合顺序结构和链式结构
/*树的孩子表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct CTNode //孩子结点
{
int child;
struct CTNode* next;
}*ChildPtr;
typedef struct //表头结构
{
TElemType data;
ChildPtr firstChild; //这里只是一个头指针,指向第一个结点
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}CTree;
(2)一维数组顺序存储
用一维数组顺序存储树中的各结点的信息,并将各结点的孩子信息组成一个单链表。孩子信息量中的每一个结点表示一个孩子结点,它由两个域组成,其中一个域表示该孩子结点在数组中的序号,另外一个域存储指向兄弟结点的指针。在结点数组中,每个元素包括结点的自身信息以及该结点的信息结点链表的头指针。
2. 小结
由于每个结点可有多个子树(无法确定子树个数),可以考虑使用多重链表来实现。
将双亲表示法和孩子表示法结合起来。分别将各结的孩子结点组成一个单链表,同时用一维数组顺序存储树中的各结点,数组元素包括结点的自身信息、双亲结点在数组中的序号以及该结点的孩子结点链表的头指针。单链表中的每一个结点表示一个孩子结点,它两个域组成,其中一个城表示该孩子结点在数组中的序号,另外一个城存储指向兄弟结点的指针。
/*树的孩子表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct CTNode //孩子结点
{
int child;
struct CTNode* next;
}*ChildPtr;
typedef struct //表头结构
{
TElemType data;
int parent;
ChildPtr firstChild; //指向第一个孩子的指针
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //r是根位置,n是结点数
}CTree;
typedef int TElemType;
typedef struct CSNode
{
TElemType data;
struct CSNode* firstchild, *rightsib;
}CSNode,*CSTree;
对于双亲表示法:我们先将双亲结点存入,我们每插入一个结点都是知道双亲结点位置的,数据可以直接插入。使用顺序存储结构更加方便;而对于孩子表示法,我们每次插入一个结点,对其子树的位置存放暂不确定,主要使用链式存储结构。
同二叉树的遍历类似,树的遍历是指按照某种顺序访问树中的每个结点,并使每个结点被访问一次且只被访问一次。
树的先根遍历与其转化后相应二叉树的先序遍历的结果序列相同;树的后根遍历与其转化后相应二叉树的中序遍历的结果序列相同。
树的遍历 | 对应二叉树的遍历 |
---|---|
先根遍历 | 先序遍历 |
后根遍历 | 中序遍历 |
因此,在孩子-兄弟链表存储结构中,树的遍历算法也可采用相应的二义树的遍历算法实现。
树的先根遍历的结果序列为 A,B,E,F,K,L,C,G,D,H, I,M,N,J。
树的后根遍历的结果序列为E,K,L,F,B,G,C,H,M,N,I,J,D,A。
层序遍历所得到的结点序列为A,B,C,D,E,F,G,H,I,J,K,L,M,N。
森林的先根遍历与其转化后相应二叉树的先序遍历的结果相同;森林的中根遍历与其转化后相应二叉树的中序遍历的结果相同;森林的后根遍历与其转化后相应二叉树的后序遍历的结果相同。
森林遍历结果=二叉树遍历结果
因此,森林的遍历算法也可采用相应的二叉树的遍历算法实现。
森林的先根遍历的结果序列为 A,B,C,D,E,F, G,H,I。
森林的中根遍历的结果序列为B,C,D,A,F,E,H,I,G。
森林的后根遍历的结果序列为 D,C,B,F,I,H,G,E,A。