PAT归纳总结——关于二叉树的一些总结

  今天是6月26日到下个月的这个时候已经考过试了,为了让自己考一个更高的分数,所以我打算把PAT的相关题型做一个总结。目前想到的方法就是将相关的题型整理到一起然后,针对这种题型整理出一些方法。

  •  二叉树的构建

  有很多题目需要自己构造一棵二叉树有的是给出层序遍历的结果然后自己来构造二叉树,有的是给出二叉树的前序和中序或者给出二叉树的中序和后序来构造二叉树(如果给出前序和后序遍历的结果我们是不能够确定一棵二叉树的,很少数特殊的情况下可以唯一确定)

  给出层序遍历的结果来构造二叉树:

 1 void buildTree() {
 2     root->value = que.front();
 3     que.pop();
 4     nodeQue.push(root);
 5     while(!que.empty()) {
 6         node parent = nodeQue.front();
 7         nodeQue.pop();
 8         node left = new Node();
 9         node right = new Node();
10         if (!que.empty()) {
11             left->value = que.front();
12             parent->left = left;
13             nodeQue.push(left);
14             que.pop();
15         }
16         if (!que.empty()) {
17             right->value = que.front();
18             parent->right = right;
19             nodeQue.push(right);
20             que.pop();
21         }
22     }
23 }

  

  给出了中序遍历和后序遍历的结果来构建二叉树:

  后一种树的构建方法是通过前序遍历和中序遍历以及后序遍历中结点的遍历次序来实现的。比如:前序遍历中第一个数字是根节点,然后找出根节点在中序遍历中的位置pos,pos左边的就是左子树,pos右边的就是右子树。然后再递归的查找左右子树,直到叶子节点为止。中序遍历和后序遍历的情况与之相同,只不过后序遍历的最后一个数字代表的是根节点。

 1 // 由中序遍历和后序遍历构建二叉树
 2 node buildTree(int l1, int r1, int l2, int r2) {
 3     if (l1 > r1 || l2 > r2) return NULL;
 4     int val = postOrder[r1];
 5     node root = new Node(val);
 6     int pos = 0;
 7     for (int i = l2; i <= r2; ++i) {
 8         if (inOrder[i] == val) {
 9             pos = i;
10             break;
11         }
12     }
13     int rightLen = r2 - pos;
14     int leftLen = pos - l2;
15     root->right = buildTree(r1 - rightLen, r1 - 1, pos + 1, r2);
16     root->left = buildTree(l1, l1 + leftLen - 1, l2, l2 + leftLen - 1);
17     return root;
18 }

   

  BST + 前序遍历构造二叉树

  还有一种情况是,题目说明了这棵树是BST(二叉搜索树),然后又给出了这棵二叉树的前序遍历序列,由此来构造这棵二叉树。(例题:1143 Lowest Common Ancestor)因为BST满足左子树都比根结点小,右子树都比根结点大的条件,所以我们可以通过前序遍历,找到根结点,然后再在前序遍历序列中找到第一个比根结点大的结点,此节点右侧(包含该结点)为右子树的结点,左侧为左子树的结点。然后,通过递归函数构建左子树和右子树,直到叶节点。

  (递归法)

 1 node buildTree(vector<int>& v, int start, int end) {
 2     if (start > end) return NULL;
 3     node root = new Node(v[start]);
 4     int pos = start + 1;
 5     while (pos <= end && v[pos] < v[start]) ++pos;
 6     if (pos == start + 1) {
 7         root->right = buildTree(v, start + 1, end);
 8     } else if (pos == end + 1) {
 9         root->left = buildTree(v, start + 1, end);
10     } else {
11         root->left = buildTree(v, start + 1, pos - 1);
12         root->right = buildTree(v, pos, end);
13     }
14     return root;
15 }

  (非递归法) 

 1 node buildTree(vector<node>& v) {
 2     node root = v[0];
 3     for (int i = 1; i < v.size(); ++i) {
 4         node temp = root;
 5         while (temp != NULL) {
 6             if (v[i]->val < temp->val) {
 7                 if (temp->left != NULL)
 8                     temp = temp->left;
 9                 else {
10                     temp->left = v[i];
11                     break;
12                 }
13             } else {
14                 if (temp->right != NULL)
15                     temp = temp->right;
16                 else {
17                     temp->right = v[i];
18                     break;
19                 }
20             }
21         }
22     }
23     return root;
24 }

 

  给出前序遍历和后序遍历来构造二叉树

   一般情况下由前序遍历和后序遍历是不能够构造出一颗二叉树的,但是在一些特殊条件下还是能够唯一确定这棵二叉树的。因为当每一个根结点既有左子树又有右子树时,那么就不会存在左右子树不确定的情况出现,这时这可二叉树也就是一棵确定的二叉树。但是我们要怎样判断一个根结点是不是既有左子树又有右子树呢?

PreOrder:   1 2 3 4 6 7 5

PostOrder:  2 6 7 4 5 3 1

  当所给的序列是上面的这种情况的时候,以前序遍历为基准来确定根结点,可知,1是这棵树的根结点,那么1后面的2一定是它的一个孩子结点。通过在后序遍历中2的位置我们可以得出,2前面没有数字,也即2不可能作为一个根节点存在。并且,2与其父亲结点之间存在其他的数字,这说名2是其父节点的一个左子树,2和1之间的数字为右子树结点,根据前序遍历的序列可以得出2后面的数字3是右子树的根结点,然后再依据此规律递归。

PreOrder: 1 2 3 4 6 7 5

PostOrder:6 7 4 5 3 2 1

  当所给的序列是上面的这种情况的时候,同样以前序遍历为基准,可以得出1为根结点,2为1的一个孩子结点,但是在后序遍历中2和1是紧挨着的。假设结点1有两个孩子结点,如果2为1的左子树,那么在后序遍历序列中2和1之间一定还会有其他的数字来充当右子树;如果2为1的右子树,那么在前序遍历的序列中,一定还会有其他的数字来充当左子树。显然,这两种情况都与所给出的序列矛盾,也即在这种情况下结点1只有一个孩子结点,这个孩子结点是右子树,还是左子树是不确定的,这也是导致了所构造出来的二叉树不唯一的根本原因。

 1 int preOrder[32], postOrder[32];
 2 int site = 0, num = 0, n;
 3 bool isUniq = true;
 4 
 5 void Build(Tree &node, int s, int e) {
 6     if (s > e) {
 7         return;
 8     }
 9     node = (Tree)malloc(sizeof(TreeNode));
10     node->val = postOrder[e];//后续遍历的最后一个节点肯定是当前子树的根节点
11     node->left = node->right = NULL;
12     site++;
13     int child = s;
14     for (; child < e; child++) {//寻找当前根节点的一个孩子
15         if (postOrder[child] == preOrder[site])break;
16     }
17     if (e - child == 1) {//当前根节点只有一个孩子,此时树不唯一
18         isUniq = false;
19         Build(node->left, s, e - 1);
20     }
21     else if (e - child > 1) {//当前根节点有俩个孩子,且这个孩子定是左孩子
22         Build(node->left, s, child);
23         Build(node->right, child + 1, e - 1);
24     }
25 }
26 
27 // 原文链接:https://blog.csdn.net/qq_40504851/article/details/100590035
  • LCA问题 (值得注意)

  刷题的过程中也遇到一些寻找二叉树中最近公共祖先结点的问题,这类问题需要我们根据题中所给的数据首先要构建出这棵二叉树(构建的方法上文已经给出),LCA(The Lowest Common Ancestor)这类问题有一个很好用的模板可以使用:

1 node lowestCommonAncestor(node root, int n1, int n2) {
2     if (!root || root->val == n1 || root->val == n2) return root;
3     node left = lowestCommonAncestor(root->left, n1, n2);
4     node right = lowestCommonAncestor(root->right, n1, n2);
5     return !left ? right : !right ? left : root;
6 }

  从根节点对这棵二叉树的左右子树进行递归遍历,当遍历到叶子结点或者某个要查询的结点的时候,就将该结点返回,如果某个根结点的左子树和右子树都非空的话则说明该节点就是所查询结点的LCA。如果一个结点为空另一个结点非空的话,则说明该非空的结点就是所要查询的LCA,也即所查寻的两个结点中一个结点是另一个结点的祖先结点。如果所查询的两个结点都是NULL的话则返回NULL。

  例题:1151 LCA in a Binary Tree, 

  思考:为什么这样写是错误的?

return left ? left: right ? right: root;

  这类问题也在PAT考试的过程中出现过,但是所考察的形式都比较简单,首先要明白大堆和小顶堆的概念,大顶堆就是根结点的值大于左子树和右子树中结点的值,小顶堆则与之相反,根结点中的value小于左子树和右子树中的值。至于,怎么构建大顶堆现在还没有遇到这样的题,之后遇到了再补上。

原文地址:https://www.cnblogs.com/h-hkai/p/13195354.html