又是一次纠结的学习过程,通常树的结构在几种常用的结构中以孩子表示法居多,课本也给了孩子表示法的创建和遍历算法。但是我在学习中对于孩子表示法的修正结构孩子链表示法产生了兴趣。于是就着手于孩子链表示法的创建存储和遍历,结果经历了漫长的探究。
首先给出树的数据结构基础知识:
/*------------------------------------------------
树的储存结构及遍历算法:
1、存储结构:1、孩子表示法(孩子数据为顺序结构,长度为树的度)
2、孩子链表示法(孩子数据为链式结构,节省空间)
3、孩子兄弟表示法(只给结点第一个左孩子和右侧第一个兄弟结点指针)
4、双亲表示法(算法不便)
2、遍历算法:先序遍历、后序遍历、层次遍历
-------------------------------------------------*/
/*-----------------数据结构(树的结构学习)--------------------------------------
#define EleType int
//孩子表示法
#define DU 3 //宏定义DU为树的度,即树的结点度的最大值
typedef struct _ChildExpres1
{
EleType data;
struct _ChildExpres1* pChildaArray[DU]; //指向所有孩子
}ChildExpres1,*pChildExpres1;
//此为一个结点的结构,所以定义是以结构体数组形式申请树的所有结点空间
//孩子链表示法
typedef struct _ChildNode
{
int num; //该结点在树结点结构体数组中的位置标号
struct _ChildNode* next; //指向下一个兄弟结点
}ChildNode,*pChildNode;
typedef struct _ChildExpres
{
EleType data;
pChildNode flchild; //指向第一个左孩子
}ChildExpres,*pChildExpres;
//此为一个结点的结构,所以定义是以结构体数组形式申请树的所有结点空间
//孩子兄弟表示法
typedef struct _ChildBrother
{
EleType data;
struct _ChildBrother* flchild; //指向第一个左孩子
struct _ChildBrother* frbrother; //指向右侧第一个兄弟结点
}ChildBrother,*pChildBrother;
//双亲表示法
typedef struct _Parent
{
EleType data;
int iParent; //双亲结点在结点数组中的位置
}Parent,*pParent;
//声明树: Parent Tree[NUM_NODE]
//所有的表示法,只要树的结点以数组顺序结构储存,则结点中的指针可以用
//整型数据指向目标结点的元素位置即可,不用使用指针型数据,利用数组特性
--------------------------------------------------------------------*/
孩子链树的建立我采用了动态建立,以广义表的形式接受字符串如:str[N] = A(B(E,F),C,D(G(I(L),J,K),H))#,字符串以#为结束标志
创建过程使用了两个堆栈。node_stack和root_stack,root_stack存放根节点和孩子树根结点元素下标。node_stack存放所有结点元素下标
具体实现思路如下:
1、循环遍历str,知道‘#’,记录字母数,和字符串字符数,申请空间字母树*结点空间,动态数组存放所有结点数据。建立两个动态数组作为堆栈,长度为字母数;
2、遍历str,一次读取str每一个字符;
1、读到字母:将字母存进树结点数组,其数组元素下标压入node_stack
2、读到‘(’ :则可知前一次读取的字符结点为根结点或子树根结点,所以前一次读取的字母在数组中的元素下标压入root_stack
3、读到‘)’ :node_stack依次出栈,直到node_stack[top] = root_stack[top],此时出栈的元素都是上一次读到的根结点的孩子结点。然后root_stack出栈一次,表示该root 结点处理完毕,此时该结点数组元素序号在node_stack栈的栈顶,相当于一般的叶节点处理了。画图可直观理解过程。
代码如下:
//以孩子链表示法为例,表现其动态建立,及遍历算法
//动态建立树 str = "A(B(E,F),C,D(G(I(L),J,K),H))#" 以str形式为例
#include<stdio.h>
#include<stdlib.h>
//--------------------数据结构--------------------------------
typedef struct _Child
{
int num;
struct _Child* pFbro; //指向下一个兄弟结点
}Child,*pChild;
typedef struct _Node
{
char data;
pChild pFlchild; //指向第一个左孩子
}Node,*pNode;
//--------------------函数实现--------------------------------
//以广义表str生成相应的树
pNode Create_Tree(char str[])
{
int top_root = -1;
int top_node = -1;
int num_str,num_char,ptr_char,i;
pNode pTree;
int *root_stack,*node_stack;
pChild ptr,root;
num_str = 0;
num_char = 0;
while(str[num_str]!='#')
{
if('A'<=str[num_str] && str[num_str]<='Z')
{
num_char++; //记录字符数
}
num_str++;
}
pTree = malloc(num_char*sizeof(Node)); //建立树结点数组
root_stack = malloc(num_char*sizeof(int)); //建立root栈
node_stack = malloc(num_char*sizeof(int)); //建立node栈
ptr_char = 0;
i = 0;
while(str[i]!='#')
{
if('A'<=str[i] && str[i]<='Z') //读取到字母,字母存进结点数组,且数组序号进node栈
{
(pTree+ptr_char)->data = str[i];
(pTree+ptr_char)->pFlchild = NULL;
top_node++;
*(node_stack+top_node) = ptr_char;
ptr_char++;
}
else if( str[i] == '(' ) //读到'(',前一次读取的字母数组元素序号进root栈
{
top_root++;
*(root_stack+top_root) = ptr_char-1;
}
else if( str[i] == ')' ) //读到')',node出栈,直到node_stack[top]==root_stack[top],再弹出一次root_stack
{
root = NULL;
while( *(node_stack+top_node) > *(root_stack+top_root) )
{
ptr = malloc(sizeof(Child));
ptr->num = *(node_stack+top_node);
ptr->pFbro = root;
root = ptr;
top_node--;
}
(pTree+(*(root_stack+top_root)))->pFlchild = root;
top_root--;
}
else ;
i++;
}
free(root_stack);
free(node_stack);
return pTree;
}
接下来是该树的遍历:企图利用堆栈,进行先序递归遍历,但未能实现,主要是兄弟结点的处理上遇到难题,若兄弟结点入栈的话,一个栈似乎不够,或者需要建立复杂的栈,既需要保存每一个结点所有孩子结点的数组元素下标值(即child->num),同时需要标出是属于哪一个根结点的,即要表达双亲关系。
附未完成的代码:
//先序递归遍历,参数:头结点 [未完成]
int Stack[100],Top_Stack;
Top_Stack = -1;
void Pre_recur_travl(pNode head)
{
int num;
pChild pc;
if(head!=NULL)
{
printf("%c\n",head->data);
if(head->pFlchild!=NULL)
{
pc = head->pFlchild;
num = pc->num;
head = head+num;
for(pc->pFbro!=NULL)
{
Top_Stack++;
Stack[Top_Stack] = pc->pFbro->num;
pc = pc->pFbro;
}
Pre_recur_travl(head);
}
else
{
}
}
}
写到一半,发现一个简单数据类型的栈无法设计出实现方法。
附教程提供的孩子表示法的创建及遍历算法:
//孩子表示法及其算法
//str[]广义表转换成孩子表示法的树结构,参数为str[]、m,m为树的度
Create_Gtr(str,m)
{
top = 0;
Gt = NULL;
i = 1; //设数组元素从1开始
while(str[i]!=NULL)
{
switch(str[i])
{
case ' ': //空格不处理
break;
case '(': //左括号,ptr和数字1进相应栈
top++
sak[top] = ptr;
num[top] = 1;
break;
case ')': //右括号,sak和num栈顶元素都出栈
top--;
break;
case ',': //逗号时,num栈顶元素+1
num[top]++;
break;
default: //字母时:建新结点,链接到sak栈顶当孩子域
ptr = malloc(size);
ptr->data = str[i];
for(j=1;j<m;j++) //m个度,为每个子结点指针附值
{
ptr->chp[j] = NULL;
}
if(Gt == NULL)
{
Gt = ptr;
}
else
{
sak[top]->chp[top] = ptr;
}
}
i++
}
return Gt;
}
/*
Gt :指向要创建的、具有标准链式存储结点的树的根节点指针
top:顺序栈的栈顶指针,初始化为0
sak:顺序栈,扫视广义表str时,该栈存放根结点,叶结点不入此栈
num:顺序栈,数值表示当前位于sak位置的根结点已有或将有第i个孩子
ptr:工作指针,指向当前建立的结点空间
data:储存结点的数据域
chp[i]:储存结点的孩子指针域 1<=i<=m
实例:
top sak栈 num栈
0 空 空
1 A 1
1 A 1
2 A,B 1,1
2 A,B 1,2
3 A,B,C 1,2,1
*/