剑指offer系列11:树的子结构
今天,借这个这个机会,先复习一下关于树的知识,以下关于树的内容全部来自于严阿姨写的《数据结构》。
一.树
树:由根结点和若干个不相交的子树构成(递归定义)。树是一种具有递归性质的数据结构。
树的结点:包含一个数据元素的内容及若干指向子树的分支。
结点的度:结点拥有的子树数成为结点的度。
叶子和分支节点:度为0的结点称为叶子或终端结点,度不为0的结点成为非终端结点或分支节点。
树的深度:树中结点的最大层次称为数的深度或高度。
有序树:如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。
森林:森林是m颗互不相交的树的集合。
二.二叉树
二叉树是每个结点之多有两颗子树并且子树有左右之分,次序不能任意颠倒。有定义可知,二叉树是有序树。
满二叉树:一颗深度为k且有2的k次方-1个结点的二叉树称为满二叉树。
完全二叉树:深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k 的满二叉树中编号从1到n的结点一一对应时,称之为完全二叉树。
平衡二叉树:又称(AVL树),它或者是一颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度只差的绝对值不超过1.
三.二叉树的遍历
遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,这实质上是对一个非线性结构进行线性化操作,使每个结点(除第一个和最后一个)在这些线性序列中有且只有一个直接前驱和直接后继。
先序遍历:1.访问根结点 2.先序遍历左子树 3.先序遍历右子树
中序遍历:1.中序遍历左子树 2.访问根结点 3.中序遍历右子树
后序遍历:1.后序遍历左子树 2.后序遍历右子树 3.访问根结点
四.线索二叉树
在二叉树的基础上加入其前驱和后继结点的指针的线索的树,称为线索二叉树。
若在程序中所用的二叉树需要经常遍历或查找结点在遍历所得线性序列中的前驱和后继,则采用线索链表作为存储结构。
五.赫夫曼树
路径上的分支数目称作路径长度,数的路径长度是从树根到每一个结点的路径长度之和。
将上述概念推广到一般情况,考虑带权的结点。则树的带权路径长度为树中所有叶子结点的带权路径长度纸之和,通常记作WPL;
假如有n个权值,尝试构造一颗有n个结叶子结点的二叉树,每个叶子节点带权为w,则其中带权长度最小的二叉树称作最优二叉树或赫夫曼树。
ok,现在来说这道题,上面说过,树是一种有递归性质的数据结构,因此树的遍历查找都很自然可以想到使用递归来完成。
在使用递归时,要考虑如下因素:1.如何结束递归 2.含有递归语句的函数应该按照递归的思路去写。
我在做这道题的时候,思路混乱,以后先想好在写代码,最好画出流程图,心中有思路再去写。
下面是这道题的代码:
1 #include<iostream> 2 using namespace std; 3 struct TreeNode { 4 int val; 5 struct TreeNode *left; 6 struct TreeNode *right; 7 TreeNode(){} 8 }; 9 class Solution { 10 public: 11 bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)//判断A树中是否存在一个子树它的根结点和B树的根结点相同 12 { 13 /* 14 if (pRoot2 = NULL)//要递归调用,所以不能这么写 15 return false; 16 else if (pRoot1 = NULL) 17 return false; 18 */ 19 if (pRoot1 == NULL || pRoot2 == NULL) 20 return false; 21 bool result = false; 22 23 if (pRoot1->val == pRoot2->val) 24 result = CompareTree(pRoot1, pRoot2); 25 if (!result)//在上一步中没找到 26 result = HasSubtree(pRoot1->left, pRoot2); 27 if (!result)//在左子树中没找到 28 result = HasSubtree(pRoot1->right, pRoot2); 29 return result; 30 31 } 32 bool CompareTree(TreeNode* pRoot1, TreeNode* pRoot2)//判断根结点相同的A的子树和B树是否具有相同的子结构 33 { 34 if (pRoot2 == NULL)//在这里要判断好两个指针之间的关系,是以指针为空作为递归的终点。 35 return true; 36 if (pRoot1 == NULL) 37 return false; 38 if (pRoot1->val != pRoot2->val) 39 return false; 40 return CompareTree(pRoot1->left,pRoot2->left) && CompareTree(pRoot1->right, pRoot2->right); 41 } 42 }; 43 int main() 44 { 45 Solution so; 46 TreeNode list[3]; 47 list[0].val = 1; 48 list[0].left = &list[1]; 49 list[0].right = &list[2]; 50 list[1].val = 2; 51 list[1].left =NULL; 52 list[1].right = NULL; 53 list[2].val = 3; 54 list[2].left = NULL; 55 list[2].right = NULL; 56 TreeNode nodeb; 57 nodeb.val=2; 58 nodeb.left=NULL; 59 nodeb.right = NULL; 60 TreeNode *p = &nodeb; 61 cout << so.HasSubtree(list,p) << endl; 62 return 0; 63 }
这个题是我参考答案的基础上写出来的。这个题无论是思路还是做法都很有代表性,答案的代码也简洁规范,很值得学习。关于二叉树的代码有很多指针,每次使用指针时,一定要问自己这个指针有没有可能是NULL!
链表和树是很重要的知识点,树的考察中二叉树是重点。树比链表稍复杂,无论从指针还是结构。因此在做树的题目一定要认真仔细+思路清晰。