孩子兄弟表示法

这种方法的主要作用在于将多叉树变为二叉树,结点数据结构如下,data 为数据域,剩下两个是指针域。firstChild 指该结点第一个孩子的地址,rightSib 为该结点第一个右兄弟的地址。这样可以快速找到长子,然后通过长子的 rightSib 找到同辈。但是无法找到双亲。

完全二叉树

除最后一层外都是和满二叉树完全一致,最后一层的结点在满二叉树中的位置和目前是一致的。

  • 具有 n 个节点的完全二叉树的深度为 [log2n] + 1

  • 如果 i>0 , 双亲是 [i/2]

  • 2i > n (n 是总结点),那么 i 无左孩子,否则左孩子为 2i

  • 2i + 1 > n ,那么 i 无友孩子,否则右孩子为 2i + 1

完全二叉树存储

由于完全二叉树的严格定义,可以用一维数组来存储一个完全二叉树,可以利用它的性质来取到一个结点双亲和儿子的下标。

链式二叉树可以节省内存,避免极端情况发生。

  1. typedef struct BinaryNode {
  2. char data;
  3. struct BinaryNode *lchild, *rchild;
  4. } BinaryNode, *BinaryTree;

前序遍历

image.png

  1. void preOrderTraverse(BinaryTree tree) {
  2. if (tree == NULL)
  3. {
  4. return;
  5. }
  6. printf("%c", tree->data);
  7. preOrderTraverse(tree->lchild);
  8. preOrderTraverse(tree->rchild);
  9. }

前序,后序,中序三种遍历方式的结点游走方式都是一样的 —— 先遍历左子树然后遍历右子树。对于传入函数的指针可以理解为一个树而不是一个结点。

image.png 》》》 image.png》》》image.png

前序遍历的特点就是节点怎么游走的数据就是怎么打印的。

中序遍历

image.png

  1. void preOrderTraverse(BinaryTree tree) {
  2. if (tree == NULL)
  3. {
  4. return;
  5. }
  6. preOrderTraverse(tree->lchild);
  7. printf("%c", tree->data);
  8. preOrderTraverse(tree->rchild);
  9. }

遍历完一个结点的左子树之后打印该结点,所以判断中序遍历的起点就是判断一个结点有没有左子树,如果有那么起点就在左子树中。如果没有那么就是起点。找出起点然后将每个子树看成独立的树来进行遍历。

image.png

起点为 G ,将 D 看成一个子树,D 遍历完左子树,遍历右子树 H。

后序遍历

image.png

  1. void preOrderTraverse(BinaryTree tree) {
  2. if (tree == NULL)
  3. {
  4. return;
  5. }
  6. preOrderTraverse(tree->lchild);
  7. preOrderTraverse(tree->rchild);
  8. printf("%c", tree->data);
  9. }

起点是左子树终点,因为首先执行的代码仍然是 preOrderTraverse(tree->lchild); D 遍历完左子树就要去遍历右子树。

树的遍历理解要领在于,从起点开始拆成最小的子树,从最小子树开始套用代码,进行理解。

二叉树的建立

已知中序遍历和其他两个任意一个遍历都可以唯一确定一棵树。

  1. Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] // preorder 和 inorder 均无重复元素

前序遍历的第一个显然是根结点,那么 3 就是根结点,接下来根据中序遍历可以确认 9 是左子树,15,20,7 是右子树。我们可以不停地找出子树的根结点来构建出树。然后我们需要做的就是为下一个递归调用准备好条件即可。

  1. /**
  2. * Definition for a binary tree node.
  3. * struct TreeNode {
  4. * int val;
  5. * struct TreeNode *left;
  6. * struct TreeNode *right;
  7. * };
  8. */
  9. struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int inorderSize){
  10. if (preorderSize <= 0) return NULL;
  11. struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
  12. root->val = preorder[0];
  13. int index = 0; // index 是左子树的大小
  14. while (index < inorderSize && inorder[index] != preorder[0]) ++index;
  15. root->right = buildTree(preorder + 1 + index, preorderSize - (index + 1), inorder + index + 1, inorderSize - (index + 1));
  16. root->left = buildTree(preorder + 1, index, inorder, index);
  17. return root;
  18. }

还有其他办法来构建二叉树

image.png

为了确认每个结点是否有左右孩子,对他进行扩展,扩展二叉树就可以做到一个遍历序列确定一颗二叉树,扩展二叉树实际上是一个完全二叉树,所以可以通过一个遍历来建立树。

  1. typedef struct BiThrNode {
  2. TElemType data;//数据域
  3. struct BiThrNode* lchild, * rchild;
  4. }BiThrNode, * BiThrTree;
  5. void CreateTree(BiThrTree* tree) {
  6. char data;
  7. scanf_s("%c", &data, 1);
  8. if (data != '#') {
  9. if (!(*tree = (BiThrNode*)malloc(sizeof(BiThrNode)))) {
  10. printf("申请结点空间失败");
  11. return;
  12. } else {
  13. (*tree)->data = data;
  14. CreateTree(&((*tree)->lchild));
  15. CreateTree(&((*tree)->rchild));
  16. }
  17. } else {
  18. *tree = NULL;
  19. }
  20. }
  21. int main() {
  22. BiThrTree t;
  23. printf("输入前序二叉树:\n");
  24. CreateTree(&t);
  25. }

递归构建树要注意的是当前函数构建的永远是当前结点,而不是它的子结点。特别体现在 19 行代码上。