数据结构与算法(十) -- 树

600 阅读8分钟

一、树的定义

树是由N个节点组成的有限集.

只有一个根节点也可以称之为树.

例如:

仅有一个A节点, 这也是树结构.

一般情况下, 树结构都是类似这个样子:

这个树中一共有11个节点, A B C D E F G H I J K. A为这个树的根节点.

1.1、树结构中基本术语

  • 节点的度: 节点的度是指节点下拥有的子树. 例如: A度为3, B度为2 C度为1
  • 树叶子: 把树中度为0的节点就称只为叶子. 例如: J K
  • 双亲节点: 一个节点的上一个节点就为双亲节点. 例如: C的双亲节点为A
  • 兄弟节点: 指的是同属于一个双亲节点的节点. 例如: EF就是兄弟节点

还有节点的高度、深度、层:

  • 高度: 指的是节点到叶子节点最长的边树. 例如: A到J最长为3, 记A的高度为3
  • 深度: 指从根节点到这里经历过几个子节点. 例如: A是根节点, A->A深度为0. B节点中, A->B经历1个节点 所以B深度为1
  • 层: 从根节点开始, 第几代子节点就为第几层. 例如: C是第二代, 层就为2

二、二叉树

一般的树中, 每一个节点可以拥有个无数个子节点.

二叉树是一个中特殊的树:

  • 非空二叉树有且只有一个根节点
  • 除根节点, 其余节点都是互不相交的子集
  • 每个节点, 至多只有两个子树
  • 二叉树中有左右之分. 例如B与C的意义是不一样的

2.1、特殊的二叉树

斜树: 所有的节点都是靠同一边的.

左斜树:

右斜树:

完全二叉树: 一个具备N个节点的二叉树按照层序编号一个接一个排列.

满二叉树: 所有的分支节点都有两个子节点, 所有叶子处于同一层. 呈一种左右对称形态. 是一个特殊的完全二叉树

三、二叉树的顺序存储结构

可以用一个数组来对树的节点依次存储. 由于树的各种形态很多.所以我们需要把某一个树按照完全二叉树的形态建立一个数组.

创建一个如下数组存储节点:

下标 1 2 3 4 5 6 7 8 9
节点 A B C D E F G H I

假如不是完全二叉树, 但是还是得按照完全二叉树的形态创建数组存储:

创建一个如下数组存储节点:

下标 1 2 3 4 5 6 7 8 9
节点 A B C D \ \ G H I

没有E F 节点, 就用特殊符号标记这个位置是一个空的.

3.1、代码实现顺序存储二叉树

定义一个树

typedef int Status;//返回状态
typedef int ElementType;//节点数据
typedef ElementType BinaryTree[MAX_TREE_SIZE];//树
ElementType NULLNODE = -1;//空节点

3.2、初始化

创建一个空树, 不存在任何节点

//初始化
Status InitBinaryTree(BinaryTree T) {
    for (int i = 0; i < MAX_TREE_SIZE; i++) {
        T[i] = NULLNODE;
    }
    return 1;
}

3.3、创建一个有数据的树

需要注意每一个非根节点, 必定有双亲节点

//创建数据
Status CreateBinaryTree(BinaryTree T, ElementType *V, int elementCount) {
    int i = 0;
    while (i < elementCount) {
        T[i] = V[i];
        if (i != 0 && T[(i + 1) / 2 - 1] == NULLNODE) {
            //出现了无双亲的非根节点
            return -1;
        }
        i++;
    }
    return 1;
}

3.4、树的清空与判断空

清空操作实际上就是对树的初始化操作. 直接调用初始化就可以

//清空
#define ClearBinaryTree InitBinaryTree

当一个树有根节点就为非空树

//二叉树是否为空
Status isEmptyBinaryTree(BinaryTree T) {
    return T[0] == NULLNODE ? 1 : 0;
}

3.5、获取树的深度

树的深度即为根节点到达最后一个叶节点的长度. 可以对一个树进行倒序遍历, 找到的第一个非空节点即为最后一个叶节点.

通过观察树结构得知: 树的每一层节点个数为: 1 2 4 6 8 .... 2^j(j为层数)

我们找到了最后一个节点所在的下标i之后, 只需要找到 2^j < i < 2^(j+1)求出j

//获取深度
int BinaryTreeDepth(BinaryTree T) {
    int j = -1;
    int i;
    for (i = MAX_TREE_SIZE - 1; i >= 0; i--) {
        if (T[i] != NULLNODE) {
            break;
        }
    }
    do {
        j++;
    } while (pow(2, j) <= i);
    
    return j;
}

3.6、查询与修改某个位置的值

树的每一层节点个数为: 1 2 4 6 8 .... 2^j(j为层数)

树的每一层的第一个节点的位置为 0 1 3 7 .... 2^(j-1)-1 (j为层数)

通过这个规律, 用层数获取当前层的首个节点的下标, 加上本层序号就可以得到想要的位置的下标. 需要注意的是数组是从0下标开始的, 所以本层序号也要减一

typedef struct {
    int level;//节点层号
    int order;//本层序号(满二叉树序号)
} Position;

//查询某个位置的值
ElementType Value(BinaryTree T, Position e) {
    return T[(int)pow(2, e.level - 1) + e.order - 2];
}

修改某个位置的值与查询类似, 但是需要注意的是修改的时候要注意这个节点是否有双亲节点, 不存在则无法修改

//修改某个位置的值
Status Assign(BinaryTree T, Position e, ElementType value) {
    int i = (int)pow(2, e.level - 1) + e.order - 2;
    if (value != NULLNODE && T[(i + 1) / 2 - 1] == NULLNODE) {
        return -1;
    }
    T[i] = value;
    return 1;
}

3.7、获取子节点与双亲节点

通过分析可以知道, 满二叉树中一层节点个数必定是下一层节点数的一半, 既然知道了一个节点在某一层, 就可以知道这个节点的子节点一定是下一层的. 一个节点对应两个子节点, 很容易得知子节点的位置.

需要注意传入的位置一定要是非空节点

//获取节点左孩子右孩子
ElementType leftChild(BinaryTree T, Position e) {
    if (Value(T, e) == NULLNODE) {
        return NULLNODE;
    }
    return T[(int)pow(2, e.level) + (e.order - 1) * 2 - 1];
}
ElementType rightChild(BinaryTree T, Position e) {
    if (Value(T, e) == NULLNODE) {
        return NULLNODE;
    }
    return T[(int)pow(2, e.level) + (e.order - 1) * 2];
}

需要注意的是, 根节点没有双亲节点. 查找思路与查找子节点相似

//获取双亲节点
ElementType superNode(BinaryTree T, Position e) {
    if (e.level == 1) {
        return -1;
    }
    return T[(int)pow(2, e.level - 2) - 1 + (e.order - 1) / 2];
}

3.8、层序遍历

顾名思义一层一层遍历, 只要是非空节点打印即可

//层序遍历
void Traverse(BinaryTree T) {
    for (int i = 0; i < MAX_TREE_SIZE; i++) {
        if (T[i] != NULLNODE) {
            printf("%d ", T[i]);
        }
    }
}

3.9、前序遍历

前序遍历路径是, 左节点优先于右节点遍历

//前序遍历
void PreTraverse(BinaryTree T, int e) {
    printf("%d", T[e]);
    if (T[2 * e + 1] != NULLNODE) {
        PreTraverse(T, 2 * e + 1);
    }
    
    if (T[2 * e + 2] != NULLNODE) {
        PreTraverse(T, 2 * e + 2);
    }
}
void PreOrderTraverse(BinaryTree T) {
    if (!isEmptyBinaryTree(T)) {
        PreTraverse(T, 0);
    }
    printf("\n");
}

3.10、中序遍历

中序遍历是先到根节点的最左的子树, 然后向上遍历到根节点, 再遍历根节点的右边

先来到H节点:

//中序遍历
void InTraverse(BinaryTree T, int e) {
    if (T[2 * e + 1] != NULLNODE) {
        InTraverse(T, 2 * e + 1);
    }
    printf("%d", T[e]);
    if (T[2 * e + 2] != NULLNODE) {
        InTraverse(T, 2 * e + 2);
    }
}
void InOrderTraverse(BinaryTree T) {
    if (!isEmptyBinaryTree(T)) {
        InTraverse(T, 0);
    }
    printf("\n");
}

3.11、后序遍历

后序遍历是从左到右, 先叶子节点再节点的方式遍历

//后序遍历
void PostTraverse(BinaryTree T, int e) {
    if (T[2 * e + 1] != NULLNODE) {
        PostTraverse(T, 2 * e + 1);
    }
    
    if (T[2 * e + 2] != NULLNODE) {
        PostTraverse(T, 2 * e + 2);
    }
    printf("%d", T[e]);
}
void PostOrderTraverse(BinaryTree T) {
    if (!isEmptyBinaryTree(T)) {
        PostTraverse(T, 0);
    }
    printf("\n");
}

其实不难发现, 在右斜树的情况下, 会造成很多空间的浪费. 所以还有一种链表的方式来实现树的存储

四、二叉树的链式存储结构

用一个结构体来存储树:

typedef struct BiTNode {
    ElementType data;//数据
    struct BiTNode *lChild, *rChild;//左右孩子
} BiTNode, *BiTree;

4.1、初始化

创建一个空树, 不存在任何节点

//初始化
Status InitListBinaryTree(BiTree *T) {
    *T = NULL;
    return 1;
}

4.2、创建一个树

因为定义的这个树结构, 是以孩子节点查找的, 所以这里传入数据的时候使用层序序列会造成很大的困扰. 这里就使用前序遍历序列来传入.

//创建
//S为前序遍历序列, 例如AB#D##C##   index为从S的哪个位置开始,为下面递归提供下标
void CreateListBinaryTree(BiTree *T, const char *S, int *index) {
    int *i = index;
    char el = S[i[0]];
    *i = *i + 1;

    if (el == '#') {
        *T = NULL;
    } else {
        *T = (BiTree)malloc(sizeof(BiTNode));
        if (!*T) {
           exit(OVERFLOW);
        }
        (*T)->data = el;
        printf("%c ", el);
        CreateListBinaryTree(&(*T)->lChild, S, i);
        CreateListBinaryTree(&(*T)->rChild, S, i);
    }
}

4.3、树判断空

只需要判断根节点是否为空

//是否为空
Status isEmptyListBinaryTree(BiTree T) {
    return T == NULL ? 1 : 0;
}

4.4.、获取深度

依次向下查找子节点, 每找到一个子节点就记录+1

//获取深度
int ListBinaryTreeDepth(BiTree T) {
    if (!T) {
        return 0;
    }
    int i, j;
    if (T->lChild) {
        i = ListBinaryTreeDepth(T->lChild);
    } else {
        i = 0;
    }
    if (T->rChild) {
        j = ListBinaryTreeDepth(T->rChild);
    } else {
        j = 0;
    }
    return i > j ? i + 1 : j + 1;
}

4.5.、前序遍历

//前序遍历
void PreOrderTraverseList(BiTree T) {
    if (T == NULL) {
        return;
    }
    printf("%c ", T->data);
    PreOrderTraverseList(T->lChild);
    PreOrderTraverseList(T->RChild);
}

4.6、中序遍历

//中序遍历
void InOrderTraverseList(BiTree T) {
    if (T == NULL) {
        return;
    }
    InOrderTraverseList(T->lChild);
    printf("%c ", T->data);
    InOrderTraverseList(T->RChild);
}

4.7、后序遍历

//后序遍历
void PostOrderTraverseList(BiTree T) {
    if (T == NULL) {
        return;
    }
    
    PostOrderTraverseList(T->lChild);
    PostOrderTraverseList(T->RChild);
    printf("%c ", T->data);
}