数据结构与算法之树的初探

313 阅读11分钟

认识树

  • 树的结构


  • 树的部分基本定义

  1. 节点:树中的一个独立单元,包含一个数据元素以及若干指向其他子树的分支
  2. 节点的度:结点拥有的子树数量成为结点的度
  3. 树的度:树内根结点度的最大值
  4. 叶子:度为零的结点称为叶子或终端结点
  5. 层次:结点的层次从根开始定义,根为第一层,根的孩子为第二层,树中任一层次等于其双亲结点的层次+1
  6. 节点的高度:节点到叶子节点的最大路径(边数)
  7. 节点的深度:根节点到这个节点的所经历的边的个数
  8. 节点的层数:节点的深度-1
  9. 树的高度:根节点的高度


二叉树

二叉树相关定义与特性

  • 定义:有且仅有一个根节点,除了根节点以外,其余节点分为两个互不相交的子集T1、T2,分别称为树T的左子树、右子树,且T1、T2都是二叉树    
  • 二叉树的特性

  1. 二叉树每个节点最多有两个子树(二叉树中不存在度大于2的节点),所以二叉树中节点的度不大于2
  2. 二叉树的子树有左右之分,次序不能颠倒
  3. 即使只有一棵树也需要区分左子树还是右子树
  • 二叉树的五中形态


  • 满二叉树:除了叶子节点外,每个节点均有左右两个子树


  • 完全二叉树对⼀颗具有n个结点的⼆叉树按层序编号,如果编号为i(1=< i <= n)的结点与同样深度的满⼆叉树中编号为i的结点⼆叉树中位置完全相同. 则这颗⼆叉树称为完全⼆叉树. 

  • 满二叉树与完全二叉树的区别
  1. ⾸先"完全" 和 "满" 的差异, 满⼆叉树⼀定是⼀个完全⼆叉树不⼀定是满的.
  2. 完全⼆叉树的所有结点和同样深度的满⼆叉树,它们按照层序编号相同的结点⼀⼀对应. 这⾥有⼀个关键词是按层序编号 
  • 完全二叉树的特性
  1. 叶⼦结点只能出现在最下两层
  2. 最下层的叶⼦⼀定集中在左部连接
  3. 倒数第⼆层,若有叶⼦节点, ⼀定都在右部连续位置
  4. 如果结点度为1, 则该结点只有左孩⼦, 既不存在只有右⼦树的情况
  5. 同样结点数的⼆叉树, 完全⼆叉树的深度最⼩; 
  • 二叉树的性质:



二叉树的顺序存储

二叉树按照结点编号依次存入数组中即可,结点不存在时,数组对应节点下标存储为空,缺点是浪费存储空间


二叉树的遍历

⼆叉树的遍(Traversing binary tree) 是指的从根结点出发,按照某种次序依次访问
⼆叉树中所有结点,使得每个结点被访问⼀次且仅被访问⼀次.
  • 前序遍历:若⼆叉树为空,则空操作返回; 否则先访问根结点,然后前序遍历左⼦树,在前序遍历右⼦树

    #pragma mark - 前序遍历(root->left->right)
    void PreTraversal(SqBiTree T, int index){
        if (EmptyBiTree(T)) {
            printf("空树\n");
        }else{
            printf("%3d",T[index]);
            //先遍历左子树
            if (T[2*index + 1] != Nil) {
                PreTraversal(T, 2*index + 1);
            }
            //再遍历右子树
            if (T[2*index + 2] != Nil) {
                PreTraversal(T, 2*index + 2);
            }
        }
    }
  • 中序遍历:若⼆叉树为空,则空操作返回; 否则从根结点开始(注意并不是先访问根结点),
    中序遍历根结点的左⼦树,然后是访问根结点,最后中序遍历右⼦树.

    #pragma mark - 中序遍历(left->root->right)
    void MidTraversal(SqBiTree T, int index){
        if (EmptyBiTree(T)) {
            printf("空树\n");
        }else{
            //先遍历左子树
            if (T[2*index + 1] != Nil) {
                MidTraversal(T, 2*index + 1);
            }
            printf("%3d",T[index]);
            //再遍历右子树
            if (T[2*index + 2] != Nil) {
                MidTraversal(T, 2*index + 2);
            }
        }
    }
    
    
  • 后序遍历:若⼆叉树为空,则空操作返回; 否则从左到右先叶⼦后结点的⽅式遍历左右⼦
    树,最后访问根结点

    #pragma mark - 后序遍历(left->right->root)
    void AfterTraversal(SqBiTree T, int index){
        if (EmptyBiTree(T)) {
            printf("空树\n");
        }else{
            //先遍历左子树
            if (T[2*index + 1] != Nil) {
                AfterTraversal(T, 2*index + 1);
            }
            //再遍历右子树
            if (T[2*index + 2] != Nil) {
                AfterTraversal(T, 2*index + 2);
            }
            printf("%3d",T[index]);
        }
    }
    
    
  • 层序遍历:就是按照二叉树的排序进行遍历

    #pragma mark - 按序号遍历
    void LevelOrderTraversal(SqBiTree T){
        if (EmptyBiTree(T)) {
            printf("\n空树\n");
        }else{
            int i = 0;
            while (T[i] != Nil) {
                printf("%3d",T[i]);
                i++;
            }
        }
    }

二叉树顺序存储完整代码

#include <stdio.h>
#include "stdlib.h"#include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define MAX_TREE_SIZE 100 /* 二叉树的最大结点数 */
typedef int Status;        /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int CElemType;      /* 树结点的数据类型,目前暂定为整型 */
typedef CElemType SqBiTree[MAX_TREE_SIZE]; /* 0号单元存储根结点  */
CElemType Nil = 0;   /*设整型以0为空 或者以 INT_MAX(65535)*/

#pragma mark 节点位置信息
typedef struct {
    //节点层
    int level;
    //本层的序号(按照满二叉树的顺序)
    int order;
}Position;
#pragma mark - 初始化二叉树
Status InitBiTree(SqBiTree T){
    for (int i = 0; i<MAXSIZE; i++) {
        //初始化二叉树只需把指定位置置空即可
        T[i] = Nil;
    }
    return OK;
}
#pragma mark - 打印节点值
void Visit(CElemType elem){
    printf("%2d",elem);
}
#pragma mark - 判断是否是空树
Status EmptyBiTree(SqBiTree T){
    if (T[0] == Nil) {
        return TRUE;
    }
    return FALSE;
}
#pragma mark - 按照层序次序输入二叉树的值,构造顺序存储二叉树
Status CreatBiTree(SqBiTree T){
    int i = 0;
    //本次只设置10个节点
    while (i<10) {
        T[i] = i + 1;
        printf("%3d",T[i]);
        //结点不为空,且无双亲结点的异常处理,其中T[(i+1)/2-1]是双亲结点
        if (i != 0 && T[(i+1)/2-1] == Nil && T[i] != Nil) {
            printf("出现无双亲的非根结点%d\n",T[i]);
            exit(ERROR);
        }
        i++;
    }
    //将数组T10个节点后的值设置成Nil
    while (i<MAXSIZE) {
        T[i] = Nil;
        i++;
    }
    return OK;
}
#pragma mark - 清空二叉树(相当于初始化二叉树)
#define ClearBiTree(x) InitBiTree(x)

#pragma mark - 获取二叉树的深度
int GetBiTreeDepth(SqBiTree T){
    if (EmptyBiTree(T)) {
        return -1;
    }
    //定义深度
    int depth = -1;
    int i = MAXSIZE - 1;
    //先找到最后的结点
    while (T[i] == Nil && i>=0) {
        i--;
    }
    do {
        depth++;
    } while (pow(2, depth) <= i);//求depth的2次幂,当depth的2次幂大于i时等到了深度,结束循环
    return depth;
}
#pragma mark - 获得处于e位置(层、序号)的节点值
void GetCelemType(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        printf("空树");
        return;
    }
    //先找层序,再找在本层中序号
    CElemType levelNum = pow(2, e.level - 1);//level从1开始,所以要减1
    printf("第%d层第%d个结点值为:%d\n",e.level,e.order,T[levelNum + e.order - 2]);
}
#pragma mark - 获取二叉树的根节点
Status Root(SqBiTree T,CElemType *e){
    if (EmptyBiTree(T)) {
        return ERROR;
    }
    printf("\n根节点是:%d\n",T[0]);
    *e = T[0];
    return OK;
}
#pragma mark - 给处于e结点位置的结点赋值
Status SetValue(SqBiTree T, Position e, CElemType type){
    //先找到e位置的结点
    int index = pow(2, e.level - 1) + e.order - 2;
    if (T[(index + 1)/2 - 1] == Nil && type != Nil) {//双亲结点不存在
        return ERROR;
    }
    if (type == Nil && (T[2*index + 1] != Nil || T[2*index + 2] != Nil)) {//赋值的结点有叶子节点并且该节点赋值为空,则直接返回
        return ERROR;
    }
    
    T[index] = type;
    return OK;
}
#pragma mark - 获取结点的双亲
CElemType GetParent(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        return Nil;
    }
    //获得该节点的值
    int index = pow(2, e.level - 1) + e.order - 2;
    CElemType type = T[index];
    for (int i = 0; i<MAXSIZE; i++) {
        if (T[i] == type) {
            return T[(i+1)/2 - 1];
        }
    }
    return Nil;
}
#pragma mark - 获取结点的左孩子
CElemType GetLeftChird(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        return Nil;
    }
    //获得该节点的值
    int index = pow(2, e.level - 1) + e.order - 2;
    CElemType type = T[index];
    for (int i = 0; i<MAXSIZE-1; i++) {
        if (T[i] == type) {
            return T[2*i + 1];
        }
    }
    return Nil;
}
#pragma mark - 获取结点的右孩子
CElemType GetRightChird(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        return Nil;
    }
    //获得该节点的值
    int index = pow(2, e.level - 1) + e.order - 2;
    CElemType type = T[index];
    for (int i = 0; i<MAXSIZE-1; i++) {
        if (T[i] == type) {
            return T[2*i + 2];
        }
    }
    return Nil;
}
#pragma mark - 获取结点的左兄弟
CElemType GetLeftBrother(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        return Nil;
    }
    //获得该节点的值
    int index = pow(2, e.level - 1) + e.order - 2;
    CElemType type = T[index];
    for (int i = 0; i<MAXSIZE-1; i++) {
        if (T[i] == type && i%2 == 0) {//相等并且是偶数才会有左兄弟,该节点是右节点才有左兄弟
            return T[i - 1];
        }
    }
    return Nil;
}
#pragma mark - 获取结点的右兄弟
CElemType GetRightBrother(SqBiTree T, Position e){
    if (EmptyBiTree(T)) {
        return Nil;
    }
    //获得该节点的值
    int index = pow(2, e.level - 1) + e.order - 2;
    CElemType type = T[index];
    for (int i = 0; i<MAXSIZE-1; i++) {
        if (T[i] == type && i%2 == 1) {//相等并且是奇数才会有右兄弟,该节点是左节点才有右兄弟
            return T[i + 1];
        }
    }
    return Nil;
}
#pragma mark - 二叉树的遍历
#pragma mark - 按序号遍历
void LevelOrderTraversal(SqBiTree T){
    if (EmptyBiTree(T)) {
        printf("\n空树\n");
    }else{
        int i = 0;
        while (T[i] != Nil) {
            printf("%3d",T[i]);
            i++;
        }
    }
}
#pragma mark - 前序遍历(root->left->right)
void PreTraversal(SqBiTree T, int index){
    if (EmptyBiTree(T)) {
        printf("空树\n");
    }else{
        printf("%3d",T[index]);
        //先遍历左子树
        if (T[2*index + 1] != Nil) {
            PreTraversal(T, 2*index + 1);
        }
        //再遍历右子树
        if (T[2*index + 2] != Nil) {
            PreTraversal(T, 2*index + 2);
        }
    }
}
#pragma mark - 中序遍历(left->root->right)
void MidTraversal(SqBiTree T, int index){
    if (EmptyBiTree(T)) {
        printf("空树\n");
    }else{
        //先遍历左子树
        if (T[2*index + 1] != Nil) {
            MidTraversal(T, 2*index + 1);
        }
        printf("%3d",T[index]);
        //再遍历右子树
        if (T[2*index + 2] != Nil) {
            MidTraversal(T, 2*index + 2);
        }
    }
}
#pragma mark - 后序遍历(left->right->root)
void AfterTraversal(SqBiTree T, int index){
    if (EmptyBiTree(T)) {
        printf("空树\n");
    }else{
        //先遍历左子树
        if (T[2*index + 1] != Nil) {
            AfterTraversal(T, 2*index + 1);
        }
        //再遍历右子树
        if (T[2*index + 2] != Nil) {
            AfterTraversal(T, 2*index + 2);
        }
        printf("%3d",T[index]);
    }
}


int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    Status iStatus;

    Position p;
    CElemType e;
    SqBiTree T;
    
    InitBiTree(T);
    CreatBiTree(T);
    printf("建立二叉树后,树空否?%d(1:是 0:否) \n",EmptyBiTree(T));
    printf("树的深度=%d\n",GetBiTreeDepth(T));
    
    p.level=3;
    p.order=2;
    GetCelemType(T, p);
    
    
    iStatus = Root(T, &e);
    if (iStatus) {
        printf("二叉树的根为:%d\n",e);
    }else
    {
        printf("树为空,无根!\n");
    }
    
    //向树中3层第2个结点位置上结点赋值99
    e = 99;
    SetValue(T, p, e);
    
    //获取树中3层第2个结点位置结点的值是多少:
    GetCelemType(T, p);
    //找到e这个结点的双亲;
    int index = pow(2, p.level - 1) + p.order - 2;
    printf("结点%d的双亲为%d_",T[index],GetParent(T, p));
    //找到p这个结点的左右孩子;
    printf("左右孩子分别为:%d,%d\n",GetLeftChird(T, p),GetRightChird(T, p));
    //找到e这个结点的左右兄弟;
    printf("结点%d的左右兄弟:%d,%d\n",e,GetLeftBrother(T, p),GetRightBrother(T, p));
    
    
    SetValue(T, p, 5);
    
    printf("\n二叉树T层序遍历:");
    LevelOrderTraversal(T);
    
    printf("\n二叉树T先序遍历:");
    PreTraversal(T, 0);
    
    printf("\n二叉树T中序遍历:");
    MidTraversal(T, 0);
    
    printf("\n二叉树T后序遍历:");
    AfterTraversal(T, 0);
    printf("\n");
    return 0;
}

二叉树的链式存储

使用链表存储二叉树,可以有效减少二叉树顺序存储造成的空间浪费问题

#include <stdio.h>
#include "stdlib.h"
#include "math.h"
#include "time.h"
#include "string.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
#define MAX_TREE_SIZE 100 /* 二叉树的最大结点数 */

typedef int Status;        /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef char CElemType;      /* 树结点的数据类型,目前暂定为char类型 */

typedef char String[24]; /*  0号单元存放串的长度 */
String str;
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;
    }
}

#pragma mark - 二叉树相关
int indexs = 1;
CElemType Nil = '\0';//'\0'为空

typedef struct BiNode{
    CElemType data;
    struct BiNode *leftChild,*rightChild;
}BiNode,*BiTree;

#pragma mark - 构造二叉树
Status InitBiTree(BiTree *T){
    *T = NULL;
    return OK;
}
#pragma mark - 销毁二叉树
Status DestroyBiTree(BiTree *T){
    if (*T) {
        if ((*T)->leftChild) {
            DestroyBiTree(&(*T)->leftChild);
        }
        if ((*T)->rightChild) {
            DestroyBiTree(&(*T)->rightChild);
        }
        free(*T);
        *T = NULL;
    }
    return OK;
}
#define ClearBiTree DestroyBiTree

#pragma mark - 创建二叉树
void CreatBiTree(BiTree *T){
    CElemType data;
    data = str[indexs++];
    if (data == '#') {
        *T = NULL;
    }else{
        *T = (BiTree)malloc(sizeof(BiNode));
        if (!*T) {
            exit(ERROR);
        }
        (*T)->data = data;
        //生成左子树
        CreatBiTree(&(*T)->leftChild);
        //生成右子树
        CreatBiTree(&(*T)->rightChild);
    }
}
#pragma mark - 是否是空二叉树
Status EmptyBiTree(BiTree T) {
    if(T)
        return FALSE;
    else
        return TRUE;
}
#pragma mark - 获得二叉树的深度
int GetDepthBiTree(BiTree T){
    if (!T) {
        return 0;
    }
    int leftDepth,rightDepth;
    if (T->leftChild) {//获取左子树的深度
        leftDepth = GetDepthBiTree(T->leftChild);
    }else{
        leftDepth = 0;
    }
    if (T->rightChild) {//获得右子树的深度
        rightDepth = GetDepthBiTree(T->rightChild);
    }else{
        rightDepth = 0;
    }
    return leftDepth>rightDepth ? leftDepth + 1 : rightDepth + 1;
}
#pragma mark - 获取二叉树的根
CElemType GetRootBiTree(BiTree T){
    if (!T) {
        return Nil;
    }
    return T->data;
}
#pragma mark - 返回p所指向的结点
CElemType GetValueBiTree(BiTree P){
    if (!P) {
        return Nil;
    }
    return P->data;
}
#pragma mark - 给p结点赋值
void SetValueBiTree(BiTree p, CElemType data){
    if (p) {
        p->data = data;
    }
}
#pragma mark - 遍历二叉树
#pragma mark - 前序遍历(root->left->right)
void PreTraversalBiTree(BiTree T){
    if (T) {
        printf("%c  ",T->data);
        //遍历左子树
        if (T->leftChild) {
            PreTraversalBiTree(T->leftChild);
        }
        //遍历右子树
        if (T->rightChild) {
            PreTraversalBiTree(T->rightChild);
        }
    }
}
#pragma mark - 中序遍历(left->root->right)
void MidTraversalBiTree(BiTree T){
    if (T) {
        //遍历左子树
        if (T->leftChild) {
            MidTraversalBiTree(T->leftChild);
        }
        printf("%c  ",T->data);
        //遍历右子树
        if (T->rightChild) {
            MidTraversalBiTree(T->rightChild);
        }
    }
}
#pragma mark - 后序遍历(left->right->root)
void AfterTraversalBiTree(BiTree T){
    if (T) {
        //遍历左子树
        if (T->leftChild) {
            AfterTraversalBiTree(T->leftChild);
        }
        //遍历右子树
        if (T->rightChild) {
            AfterTraversalBiTree(T->rightChild);
        }
        printf("%c  ",T->data);
    }
}
int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    BiTree T;
    CElemType e1;
    
    InitBiTree(&T);
    
    StrAssign(str,"ABDH#K###E##CFI###G#J##");
    
    CreatBiTree(&T);
    printf("二叉树是否为空%d(1:是 0:否),树的深度=%d\n",EmptyBiTree(T),GetDepthBiTree(T));
    
    e1= GetRootBiTree(T);
    printf("二叉树的根为: %c\n",e1);
    
    printf("\n前序遍历二叉树:");
    PreTraversalBiTree(T);
    
    printf("\n中序遍历二叉树:");
    MidTraversalBiTree(T);
    
    printf("\n后序遍历二叉树:");
    AfterTraversalBiTree(T);
    
    printf("\n");
    return 0;
}