08--二叉树的链式实现

897 阅读6分钟

# 08--树的基础知识是已经介绍了树的基础知识,下面来实现一下二叉树的链式存储。

一、二叉树的特征

  • 1.二叉树的第i层上,最多有2的(i-1)次方个结点;
  • 2.深度为k的树最多有(2的k+1次方)-1个结点;
  • 3.有n个结点的完全二叉树的深度为(log以2为底n的对数)+1;
  • 4.对有n个结点的完全二叉树,如果按照从上到下,从左到右的顺序的顺序从1开始对所有的结点编号,则对任意序号为i的结点有:
  • (1).如果i=1,则序号为i的结点为根结点,根结点无双亲结点;
  • (2).如果i>1,则序号为i的结点的双亲结点的序号为i/2
  • (3).如果2*i <= n,则序号为i的结点的左孩子的序号为2*i
  • (4).如果2*i > n,则序号为i的结点无左孩子;
  • (5).如果2*i + 1 <= n,则序号为i的结点的右孩子的序号为2*i+1;
  • (6).如果2*i + 1 > n,则序号为i的结点无右孩子。

二、二叉树链式实现的设计

1.准备工作

定义一些状态和数据类型,用户二叉树的顺序实现过程的使用。

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

//存储空间初始分配量
#define MAXSIZE 100

//Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int Status;

int indexs = 1;

typedef char String[24]; //0号单元存放串的长度

String str;

typedef char CElemType;

CElemType Nil=' '; //字符型以空格符为空

2.二叉树链式存储的结点设计

typedef struct BiTNode  //结点结构
{
    CElemType data;     //数据域

    struct BiTNode *lchild,*rchild; //指针域

}BiTNode,*BiTree;

指针域的lchild指向左孩子、rchild指向右孩子

3.初始化二叉树

Status InitBiTree(BiTree *T)
{
    *T=NULL;
    return OK;
}

初始二叉树为空,所以链表T指向NULL

4.创建二叉树

Status StrAssign(String T,char *chars)
{
    int i;
    if(strlen(chars)>MAXSIZE)
        return ERROR;
    else
    {
        T[0]=strlen(chars);
        for(i=1;i<=T[0];i++)
        {
            T[i]=*(chars+i-1);
        }

        return OK;
    }
}
  • 1.这个函数用来将一个字符串保存到一个数组T中,数组的第0个位置保存了数组T的长度
  • 2.String是一个长度为24的字符数组,T是用String声明的一个字符数组;
  • 3.StrAssign生成一个str数组,它的内容为:"ABDH#K###E##CFI###G#J##",其中‘#’表示二叉树的当前结点为
void CreateBiTree(BiTree *T){
    CElemType ch;

    //获取字符
    ch = str[indexs++];

    //判断当前字符是否为'#'
    if (ch == '#') {
        *T = NULL;
    }else
    {
        //创建新的结点
        *T = (BiTree)malloc(sizeof(BiTNode));

        //是否创建成功
        if (!*T) {
            exit(OVERFLOW);
        }
        
        /* 生成根结点 */
        (*T)->data = ch;

        //构造左子树
        CreateBiTree(&(*T)->lchild);

        //构造右子树
        CreateBiTree(&(*T)->rchild);
    }
}
  • 1.使用str数组生成一个二叉树;
  • 2.indexs从1开始,因为str数组的第0个位置保存着str数组的长度
  • 3.读取str中的数据,如果为'#',则当前结点为NULL;否则创建一个新结点,保存str中的对应数据;
  • 4.采用递归的方式依次构建左右孩子

5.销毁二叉树

void DestroyBiTree(BiTree *T)
{
    if(*T)
    {
        //有左孩子
        if((*T)->lchild)
            DestroyBiTree(&(*T)->lchild); //销毁左子树

        //有右孩子
        if((*T)->rchild)
            DestroyBiTree(&(*T)->rchild); //销毁右子树
        
        free(*T); //释放根结点

        *T= NULL; //空指针赋0
    }
}
  • 1.采用递归的方式遍历所有结点;
  • 2.先拿到左右孩子,再释放当前结点,然后递归子树的根结点
  • 3.销毁后的二叉树所对应的链表T指向NULL

6.二叉树判空

Status BiTreeEmpty(BiTree T)
{
    if(T)
        return FALSE;
    else
        return TRUE;
}
  • 1.根结点不存在即表示二叉树为空

7.二叉树的深度

int BiTreeDepth(BiTree T){
    int i,j;
    if(!T)
        return 0;

    //计算左孩子的深度
    if(T->lchild)
        i = BiTreeDepth(T->lchild);
    else
        i=0;

    //计算右孩子的深度
    if(T->rchild)
        j = BiTreeDepth(T->rchild);
    else
        j=0;
    //比较i和j
    return i>j ? i+1 : j+1;
}
  • 1.链式存储下的二叉树的深度计算,需要依次对左右子树进行遍历,直到左右子树为空时深度+1,如此递归最终得到了左右子树的深度,比较左右子树的深度,取较大者;
  • 2.根结点的深度为0

8.获取二叉树的根结点

CElemType Root(BiTree T){
    if (BiTreeEmpty(T))
        return Nil;
    return T->data;
}

链表T指向的结点即为根结点

三、二叉树的遍历

与顺序存储不同的是,链式存储的二叉树遍历方式有前序遍历、中序遍历和后序遍历。

1.前序遍历

1.解释说明

image.png

  • 1.如上图,按照1~9的顺序遍历二叉树中的数据;
  • 2.规则:先打印双亲节点,再打印左孩子,最后打印右孩子

2.代码实现

void PreOrderTraverse(BiTree T)
{
    if(T==NULL)
        return;

    printf("%c",T->data);//显示结点数据,可以更改为其它对结点操作

    PreOrderTraverse(T->lchild); //再前序遍历左子树

    PreOrderTraverse(T->rchild); //最后前序遍历右子树
}
  • 1.先打印当前结点,再递归左子树,最后递归右子树
  • 2.递归结束的条件是当前结点为NULL,即遍历到叶子结点了。

2.中序遍历

1.解释说明

image.png

  • 1.如上图,按照1~9的顺序遍历二叉树中的数据;
  • 2.规则:先打印左孩子,再打印双亲结点,最后打印右孩子

2.代码实现

void InOrderTraverse(BiTree T)
{
    if(T==NULL)
        return;

    InOrderTraverse(T->lchild); //中序遍历左子树

    printf("%c",T->data); //显示结点数据,可以更改为其它对结点操作

    InOrderTraverse(T->rchild); //最后中序遍历右子树
}
  • 1.先递归左子树,再打印当前结点,最后递归右子树
  • 2.递归结束的条件是当前结点为NULL,即遍历到叶子结点了。

3.后序遍历

1.解释说明

image.png

  • 1.如上图,按照1~9的顺序遍历二叉树中的数据;
  • 2.规则:先打印左孩子,再打印右孩子,最后打印双亲结点

2.代码实现

void PostOrderTraverse(BiTree T)
{
    if(T==NULL)
        return;

    PostOrderTraverse(T->lchild); //先后序遍历左子树

    PostOrderTraverse(T->rchild); //再后序遍历右子树

    printf("%c",T->data);//显示结点数据,可以更改为其它对结点操作
}
  • 1.先递归左子树,再递归右子树,最后再打印根结点
  • 2.递归结束的条件是当前结点为NULL,即遍历到叶子结点了。

四、总结

二叉树的前序遍历、中序遍历和后序遍历描述的就是在使用递归遍历时什么时机打印双亲结点。规则如下:

  • 前序遍历:先打印,再递归左子树,最后递归右子树;
  • 中序遍历:先递归左子树,再打印,然后递归右子树;
  • 后序遍历:先递归左子树,再递归右子树,最后打印。