[数据结构] 树、森林的遍历

树的遍历

树的遍历方式有先根遍历和后根遍历。在下面树的遍历中,采用的都是孩子兄弟表示法构建的树。

树的先根遍历

树的先根遍历步骤

先根遍历就是先访问树的根节点,然后再依次访问树的孩子们。在这里我们需要用递归函数来实现树的先根遍历,先打印当前节点的数据,然后再递归访问其第一个孩子,再递归访问当前节点的兄弟。注意树的根节点是没有兄弟的,在第一层递归中实际只会递归访问其 firstchild,这一点在创建树的过程中也需注意。
递归函数停止递归的边界条件很简单,遇到空指针 NULL 停止即可。

假设函数名为 CStree_preorder ,则大致步骤如下:
(1)print(root->data)
(2)CStree_preorder(root->firstchild)
(3)CStree_preorder(root->nextsibling)

树的先根遍历图解

(1)

(2)

(3)

(4)

(5)

(6)

(7)

(8)

树的先根遍历小技巧

树的先根遍历可以看成是以根节点为起点,围绕整个树跑一圈,所经历的节点值的序列就是树的先根遍历的结果(重复出现过的节点值不再加入到序列中)。这一点和二叉树的前序遍历很像。

树的先根遍历代码

void CSTree_Show_Pre_Order(CSTree T){
    if(T == NULL)
        return;
    
    printf("%c ", T->data);
    CSTree_Show_Pre_Order(T->firstchild);	
    CSTree_Show_Pre_Order(T->nextsibling);
}

树的后根遍历

树的后根遍历步骤

后根遍历的顺序是先访问根节点的孩子们,然后访问根节点。我们还是用递归函数来解决,先递归访问当前节点的第一个孩子,然后打印当前节点的数据,最后再递归访问当前节点的兄弟。递归函数停止递归的边界条件很简单,遇到空指针 NULL 停止即可。

假设函数名为 CStree_postorder ,则大致步骤如下:
(1)CStree_postorder(root->firstchild)
(2)print(root->data)
(3)CStree_postorder(root->nextsibling)

树的后根遍历图解

(1)

(2)

(3)

(4)

(5)

(6)

(7)

树的后根遍历小技巧

树的后根遍历可以看成是剪葡萄一样,将节点依次剪下后得到的序列就是树的后根遍历的结果,这一点和二叉树的后序遍历有点相似。

树的后根遍历代码

void CSTree_Show_Post_Order(CSTree T){
    if(T == NULL)
        return;

    CSTree_Show_Post_Order(T->firstchild);
    printf("%c ", T->data);
    CSTree_Show_Post_Order(T->nextsibling);
}

树的遍历与二叉树的遍历

树的先根遍历与二叉树的前序遍历

由于我们使用的是孩子兄弟表示法构建的树,实际上我们将树以二叉链式的结构存储了起来,其本质上已经转换为一棵二叉树。树的 firstchild 对应了二叉树的 leftchild,树的 nextsibling 对应了二叉树的 rightchild

结合上文先根遍历的代码和其对应关系,我们可以发现其递归操作的顺序和二叉树的前序遍历是一样的。所以我们其实可以先将树转换为二叉树,然后进行前序遍历,得到的遍历结果和树的先根遍历是一样的。

树的后根遍历与二叉树的中序遍历

同理,结合上文后根遍历的代码和其对应关系,我们可以发现其递归操作的顺序和二叉树的中序遍历是一样的。所以我们其实可以先将树转换为二叉树,然后进行中序遍历,得到的遍历结果和树的后根遍历一样。



森林的遍历

森林的先序遍历

森林的先序遍历步骤

森林的先序遍历顺序即按照森林的每棵树的顺序,对每一棵树进行先根遍历。

森林的先序遍历代码

//森林的先序遍历
void Forest_Show_Pre_Order(Forest F){
    for(int i = 0; i < F->n; i++)
        CSTree_Show_Pre_Order(F->ct[i]);
}

森林的中序遍历

森林的中序遍历步骤

森林的先序遍历顺序即按照森林的每棵树的顺序,对每一棵树进行后根遍历。

森林的中序遍历代码

//森林的中序遍历
void Forest_Show_Post_Order(Forest F){
    for(int i = 0; i < F->n; i++)
        CSTree_Show_Post_Order(F->ct[i]);
}

森林的遍历与二叉树的遍历

森林的先序遍历与二叉树的前序遍历

森林在转换为二叉树的过程中,将每个树根节点用 rightchild 进行了连接。转换成二叉树递归遍历完根节点的左子树时,接下来递归遍历其右子树,而此右子树也是森林中第二个树的根节点,以此类推,可以得出转换成二叉树的前序遍历正好就是森林先序遍历的结果。

森林的先序遍历

转换为二叉树的前序遍历

森林的中序遍历与二叉树的中序遍历

同理,森林转换为二叉树的中序遍历,正好对应了森林中序遍历的结果。

森林的中序遍历

转换为二叉树的中序遍历



程序测试

程序代码

#include<stdio.h>
#include<stdlib.h>
#define MAX 100

typedef char Elemtype;
//树 (孩子兄弟表示法)
typedef struct CSNode{

    Elemtype data;
    struct CSNode *firstchild;      //第一个孩子
    struct CSNode *nextsibling;     //下一个兄弟

}CSNode, *CSTree;


//二叉树 结构体
typedef struct BiNode{

    Elemtype data;
    struct BiNode *leftchild;       //左儿子
    struct BiNode *rightchild;      //右儿子

}BiNode, *BinaryTree;              


//森林 结构体
typedef struct {

    CSTree ct[MAX];
    int n;   //树的个数
	
}forest, *Forest;



/*—————————————————————————————————————————————————————————————————————————————————————*/
//创建 树
CSTree Create_CSTree(){
    Elemtype data;
    CSTree T;
    scanf("%c", &data);                       //输入节点数据
    getchar();

    if(data == '#')        //输入 - 以停止创建子树
        return NULL;
    T = (CSTree)malloc(sizeof(CSNode));
    T->data = data;

    printf("输入 %c 的第一个孩子数据(#停止): ", data);  //递归创建
    T->firstchild = Create_CSTree();
    printf("输入 %c 的下一个兄弟数据(#停止): ", data);  
    T->nextsibling = Create_CSTree();
 
    return T;
}



/*—————————————————————————————————————————————————————————————————————————————————————*/
//树 转化为 二叉树
BinaryTree CSTree_Transform_to_BinaryTree(CSTree ct){
    if(!ct) return NULL;

    BinaryTree T = (BinaryTree)malloc(sizeof(BiNode));
    T->data = ct->data;
    //相当于将left变为firstchild, 将right变为nextsibling 本质的形态没有改变
    T->leftchild = CSTree_Transform_to_BinaryTree(ct->firstchild);
    T->rightchild = CSTree_Transform_to_BinaryTree(ct->nextsibling);

    return T;
}


//二叉树 转化 为树
CSTree BinaryTree_Transform_to_CSTree(BinaryTree bt){
    if(!bt)	return NULL;

    CSTree T = (CSTree)malloc(sizeof(CSNode));
    T->data = bt->data;
    //相当于将firstchild变为left, 将nextsibling变为right 本质的形态没有改变
    T->firstchild = BinaryTree_Transform_to_CSTree(bt->leftchild);
    T->nextsibling = BinaryTree_Transform_to_CSTree(bt->rightchild);

    return T;
}


//森林 转化为 二叉树
BinaryTree Forest_Transform_to_BinaryTree(CSTree ct[], int low, int high){
    if(low > high)	return NULL;

    //每个树变成二叉树
    BinaryTree T = CSTree_Transform_to_BinaryTree(ct[low]);  
    //通过rightchild连接每一个二叉树的根节点
    T->rightchild = Forest_Transform_to_BinaryTree(ct, low + 1, high);

    return T;
}


//二叉树 转化为 森林
Forest BinaryTree_Transform_to_Forest(BinaryTree bt){
    Forest F = (Forest)malloc(sizeof(forest));
    BinaryTree p = bt;	
    BinaryTree q = NULL;

    int count = 0;
    while(p){
	q = p->rightchild;    //q指向要切断连接的下一个节点(即其右儿子)
	p->rightchild = NULL; //切断连接 形成单独的树

	F->ct[count++] = BinaryTree_Transform_to_CSTree(p);//二叉树 转化为 树存到森林中
	p = q;    //p指向下一个节点 重复操作
    }

    F->n = count; //记录森林中 树的个数
    return F;
}



/*—————————————————————————————————————————————————————————————————————————————————————*/
//前序 递归遍历二叉树
void BinaryTree_Show_Pre_Order(BinaryTree T){
    if(T == NULL)
	return;

    printf("%c ", T->data);	
    BinaryTree_Show_Pre_Order(T->leftchild);
    BinaryTree_Show_Pre_Order(T->rightchild);
}


//中序 递归遍历二叉树
void BinaryTree_Show_Infix_Order(BinaryTree T){
    if(T == NULL)
        return;

    BinaryTree_Show_Infix_Order(T->leftchild);
    printf("%c ", T->data);
    BinaryTree_Show_Infix_Order(T->rightchild);
}


//后序 递归遍历二叉树
void BinaryTree_Show_Post_Order(BinaryTree T){
    if(T == NULL)
	return;

    BinaryTree_Show_Post_Order(T->leftchild);
    BinaryTree_Show_Post_Order(T->rightchild);
    printf("%c ", T->data);
}



/*—————————————————————————————————————————————————————————————————————————————————————*/
//树的先根遍历  (相当于将firstchild当做left, 将nextsibling当做right)
void CSTree_Show_Pre_Order(CSTree T){
    if(T == NULL)
	return;
    
    printf("%c ", T->data);
    CSTree_Show_Pre_Order(T->firstchild);	
    CSTree_Show_Pre_Order(T->nextsibling);
}


//树的后根遍历 写法和二叉树的中序遍历一样(相当于将firstchild当做left, 将nextsibling当做right)
void CSTree_Show_Post_Order(CSTree T){
    if(T == NULL)
	return;

    CSTree_Show_Post_Order(T->firstchild);
    printf("%c ", T->data);
    CSTree_Show_Post_Order(T->nextsibling);
}



/*—————————————————————————————————————————————————————————————————————————————————————*/
//森林的先序遍历
void Forest_Show_Pre_Order(Forest F){
    for(int i = 0; i < F->n; i++)
	CSTree_Show_Pre_Order(F->ct[i]);
}


//森林的中序遍历
void Forest_Show_Post_Order(Forest F){
    for(int i = 0; i < F->n; i++)
	CSTree_Show_Post_Order(F->ct[i]);
}



/*—————————————————————————————————————————————————————————————————————————————————————*/
int main(){
    CSTree F[3];
    for(int i = 0; i < 3; i++){
        printf("创建第 %d 棵树 输入根节点(注意根节点无兄弟):\n", i + 1);
        F[i] = Create_CSTree();
        printf("\n");
    }

    printf("第一棵普通树后根遍历: \n");
    CSTree_Show_Post_Order(F[0]);
    printf("\n");

    printf("第二棵普通树先根遍历: \n");
    CSTree_Show_Pre_Order(F[1]);
    printf("\n");

    printf("三棵树组成的森林转化为二叉树之后中序遍历: \n");
    //如果是一个Forset结构体指针F  Foresr_Transform_to_BinaryTree(F->ct, 0, (F->n) - 1)
    BinaryTree bt = Forest_Transform_to_BinaryTree(F, 0, 2);
    BinaryTree_Show_Infix_Order(bt);
    printf("\n");

    printf("二叉树再次转换为森林之后先根遍历: \n");
    Forest backF = BinaryTree_Transform_to_Forest(bt);
    Forest_Show_Pre_Order(backF);
    printf("\n");
}

程序运行结果

posted @ 2023-02-04 23:27  MarisaMagic  阅读(1868)  评论(0编辑  收藏  举报