树和二叉树(含前序遍历和后序遍历案例实现)

树的双亲表示法结点结构定义

优点:根据结点的parent指针很容易找到它的双亲结点,时间复杂度为o(1)

#include <stdio.h>
#include <stdlib.h>
#define MAX_TREE_SIZE 100
typedef int TElemType; //树结点的数据类型,目前暂定为整型
typedef struct PTNode //结点结构
{
    
    
    TElemType data; //结点数据
    int parent; //双亲位置
} PTNode;
typedef struct  //树结构
{
    
    
    PTNode nodes[MAX_TREE_SIZE]; //结点数组
    int r,n; //根的位置和结点数
} PTree;

树的孩子表示法结点结构定义

树中存在多棵子树的根结点,考虑使用多重链表,即每个结点有多个指针域,其中每个指针指向一颗子树的根结点,称为多重链表表示法
方案一: 指针域的个数等于树的度
缺点:对于树中各结点的度相差很大时,显然是很浪费时间的,因为很多的结点的指针域都是空的,但是如果树的各结点度相差很小时,意味着空间被充分利用了
方案二:按需分配空间,每个结点指针域的个数等于该结点的度,并专门取一个位置来存储结点指针域的个数
优点:克服了浪费空间的缺点;缺点:由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,运算上会带来时间上的损耗
孩子表示法

  • 把每个孩子的结点排列起来,以单链表作为存储空间,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针组成一个线性表,采用顺序存储结构,放进一个一维数组
  • 孩子链表的孩子结点(存储某个结点在表头数组中的下标;指针域,用来存储指向某结点的下一个孩子结点的指针),表头数组的表头结点(数据域,头指针域存储该结点的孩子链表的头指针)
  • 优点:查找某个结点的孩子/兄弟,只需要查找该结点的孩子单链表,比较方便
  • 缺点:需要整棵树遍历才能直到结点的双亲,可以在表头结构中加入结点的parent指针*/
//树的孩子表示法结构定义
#define MAX_TREE_SIZE 100
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;  //根的位置和结点数
}CTree;

孩子兄弟表示法

  • 原理:任意一棵树,结点的第一个孩子如果存在就是唯一的,右兄弟也是唯一的,可以设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟
  • 优点:便于查找某个结点的某个孩子,只需要通过firstchild找到结点的长子,再通过长子结点的rightsib找到二弟
  • 缺点:不容易找到结点的双亲,但是可以通过增加一个parent指针域来解决快速查找双亲的问题
typedef struct CSNode
{
    
    
    TElemType data;
    struct CSNode *firstchild, *rightsib;
}CSNode,*CSTree;

二叉树

1.顺序存储结构: 使用一维数组存储二叉树的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系
2.二叉链表:二叉树的每个结点最多只有两个孩子,因此设计一个数据域和两个指针域,称为链表。如果有需要,可以增加一个指向其双亲的指针域,那就称为三叉链表

//二叉树的二叉链表结点结构
typedef struct BiTNode //结点结构
{
    
    
    TElemType data;  //结点数据
    struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;

遍历二叉树

原理:从根结点出发,按照某种次序一次访问二叉树中所有结点,使得每个结点被访问一次且被访问一次(访问,次序)
1.访问:根据时间的需求来确定具体要做什么,比如对每个结点进行计算/打印等
2.遍历方法
我们用图形的方式表现树的结构,比较直观和容易理解,但是对于计算机来说,它只有循环、判断等方式,只会处理线性序列,遍历方法都是将树中的结点变成某种意义的线性序列

  • 前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树,eg.二叉树ABCDEFGHI的前序遍历为ABDGHCEIF
  • 层序遍历:若二叉树为空,则空操作返回,否则先访问根结点,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问,二叉树ABCDEFGHI的层序遍历为ABDGHCEIF
  • 后序遍历:若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点
  • 中序遍历:若二叉树为空,则空操作返回,否则从根结点开始(注意不是先访问根结点),中序遍历根结点的左子树,然后是访问跟结点,最后中序遍历右子树
  • 代码使用案例
    图片来源:大话数据结构P187
//以下代码都是递归函数
//前序遍历:eg.二叉树ABCDEFGHI的前序遍历为ABDGHCEIF
void PreOrderTraverse(BiTree T)
{
    
    
    if (T==NULL)
        return;
    printf("%c",T->data); //显示结点数据,可以更改为其他对结点操作
    PreOrderTraverse(T->lchild); //前序遍历左子树
    PreOrderTraverse(T->rchild); //前序遍历右子树
}
//中序遍历
void InOrderTraverse(BiTree T)
{
    
    
    if (T==NULL)
        return;
    InOrderTraverse(T->lchild); //中序遍历左子树(遍历直到碰到一个左子树的叶子结点)
    printf("%c",T->data); //显示结点数据,可以更改为其他对结点操作
    InOrderTraverse(T->rchild); //中序遍历右子树
}
//后序遍历
void PostOrderTraverse(BiTree T)
{
    
    
    if (T==NULL)
        return;
    PostOrderTraverse(T->lchild); //后序遍历左子树(遍历直到碰到一个左子树的叶子结点)
    PostOrderTraverse(T->rchild); //后序遍历右子树
    printf("%c",T->data); //显示结点数据,可以更改为其他对结点操作
}
//二叉树的建立,将二叉树中每个空指针引出一个虚结点,其值为一特定值如#
void Create_BiTree(BiTree * T){
    
    
    TElemType ch;
    scanf("%c",&ch);  //“%c”表示输入数据的类型格式 &表示取地址 &ch表示输入数据后存到ch里面等于给ch赋值。
    if(ch == '@'){
    
    
        *T = NULL;
    }
        //# 表示构造结束
    else if(ch == '#'){
    
    
        return ;
    }
        //排除以上两种情况,则为有数据的结点,对其进行构造
    else{
    
    
        *T = (BiTree)malloc(sizeof(BiTNode));
        (*T)->data = ch;  //生成根结点
        //继续构造其左右孩子结点
        Create_BiTree(&(*T)->lchild);
        Create_BiTree(&(*T)->rchild);
    }
}

int main(){
    
    
    BiTree T;
    printf("input PreOrder str:");
    //AB@D@@C@@#
    //构造二叉树
    Create_BiTree(&T);
    printf("\n");
    //分别按照先序、中序、后序的方式遍历二叉树
    printf("preorder list of  T :");
    PreOrderTraverse(T);
    printf("\nInOrder list of T :");
    InOrderTraverse(T);
    printf("\nPostOrder list of T:");
    PostOrderTraverse(T);

猜你喜欢

转载自blog.csdn.net/weixin_43464554/article/details/113590888