是否同一棵二叉搜索树
是否同一棵二叉搜索树
给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果。于是对于输入的各种插入序列,你需要判断它们是否能生成一样的二叉搜索树。
输入格式:
输入包含若干组测试数据。每组数据的第1行给出两个正整数N (≤)和L,分别是每个序列插入元素的个数和需要检查的序列个数。第2行给出N个以空格分隔的正整数,作为初始插入序列。最后L行,每行给出N个插入的元素,属于L个需要检查的序列。
简单起见,我们保证每个插入序列都是1到N的一个排列。当读到N为0时,标志输入结束,这组数据不要处理。
输出格式:
对每一组需要检查的序列,如果其生成的二叉搜索树跟对应的初始序列生成的一样,输出“Yes”,否则输出“No”。
输入样例:
4 2 3 1 4 2 3 4 1 2 3 2 4 1 2 1 2 1 1 2 0
输出样例:
Yes
No
No
解题思路
对于输入的两组不相同的序列,来判断这两个序列所构成的树是否为同一颗BST,这里提供两种不同的解法。
第一种是最直接,最容易想到的,就是用这两组序列来分别构造出两颗不同的二叉树,再同时对这两颗树进行遍历,来判断是否为同一颗二叉树。
程序的框架就是,先按第一行给出的序列来构造出一颗树T1,这颗树是需要和后面的L颗树进行比较的。然后循环L次,每次输入一组序列,用同样的方法来构造这组序列对应的BST,然后再与T1比较,如果这两颗BST是一样的,那么输出"Yes",否则输出"No"。
当然,我们还需要创建一个构造树的函数,以及比较两颗树是否相同的函数。
AC代码如下:
1 #include <cstdio> 2 3 struct TNode { 4 int data; 5 TNode *left, *right; 6 }; 7 8 TNode *createTree(int n); 9 TNode *insert(TNode *T, int v); 10 bool isSameTree(TNode *T1, TNode *T2); 11 void freeTree(TNode *T); 12 13 14 int main() { 15 int n; 16 scanf("%d", &n); 17 18 while (n) { 19 int m; 20 scanf("%d", &m); 21 22 TNode *T1 = createTree(n); // T1与后面的L颗树比较 23 24 while (m--) { 25 TNode *T2 = createTree(n); 26 printf("%s\n", isSameTree(T1, T2) ? "Yes" : "No"); 27 freeTree(T2); // 与T1比较完后,把构造的树释放掉 28 } 29 30 freeTree(T1); // 与L颗树比较完后,再释放T1 31 32 scanf("%d", &n); 33 } 34 35 return 0; 36 } 37 38 TNode *createTree(int n) { //构造树的函数,n代表树的节点个数 39 TNode *T = NULL; 40 for (int i = 0; i < n; i++) { 41 int v; 42 scanf("%d", &v); 43 44 T = insert(T, v); // 把输入的值按BST的规则插入到树中 45 } 46 47 return T; // 返回这颗树的根节点 48 } 49 50 TNode *insert(TNode *T, int v) { // 该递归函数的作用是把值插在给定的树中,并返回插入后的新树的根节点 51 if (T == NULL) { // 如果根节点为空,那么生成一个节点,插入的值变成根节点 52 T = new TNode; 53 T->data = v; 54 T->left = T->right = NULL; 55 } 56 else { 57 if (v < T->data) T->left = insert(T->left, v); // 如果插入的值比根节点的值小,则把值插在左子树,并返回左子树的根节点 58 else T->right = insert(T->right, v); // 如果插入的值比根节点的值大,则把值插在右子树,并返回右子树的根节点 59 } 60 61 return T; // 返回根节点 62 } 63 64 bool isSameTree(TNode *T1, TNode *T2) { // 核心函数,用来判断两颗树是否相同 65 bool ret; 66 67 if (T1 == NULL && T2 == NULL) { // 如果两颗树都为空,这两颗树肯定是一样的(都为空) 68 ret = true; 69 } 70 else if (T1 && T2) { // 如果两颗树的根节点都存在 71 if (T1->data == T2->data) { // 如果根节点的值相同 72 73 // 还需要判断左两颗树的左子树是否相同,右子树是否相同,而判断左右子树是否相同和判断两颗树是否相同的方法是一样的,所以使用递归 74 // 只有两颗树的左右子树都相同,这两颗树才会相同 75 ret = isSameTree(T1->left, T2->left) && isSameTree(T1->right, T2->right); 76 } 77 else { // 如果根节点的值不同,那么这两颗树不相同 78 ret = false; 79 } 80 } 81 else { // 如果一颗树为空,而另外一颗树不为空,那么这两棵树肯定是不一样的 82 ret = false; 83 } 84 85 return ret; 86 } 87 88 void freeTree(TNode *T) { // 释放节点,本质就是后序遍历 89 if (T) { 90 freeTree(T->left); // 先把左子树的节点释放 91 freeTree(T->right); // 再把右子树的节点释放 92 delete T; // 最后才释放根节点 93 } 94 }
下面的方法是何老师给出的。
这个方法只需要构造出一颗树,也就是构造第一行序列的那颗树。而后面L组序列不需要构造树。
比较树的方法是,让比较的序列的每一个元素去走一遍那颗树,直到找到相同值得那个节点。如果途中经过某一个之前没有访问过节点,并且这个节点的值和这个元素的值不相同,那么就可以判定这两组序列所构造的树不是同一颗二叉搜索树,之后的元素不再需要走这颗树了。
所以,我们的节点要包含一个标识域,用来记录该节点有没有被访问过。
AC代码如下:
1 #include <cstdio> 2 3 struct TNode { 4 int data; 5 TNode *left, *right; 6 bool flag; // 标识域,用来标记该节点在比较的时候有没有被访问过 7 }; 8 9 TNode *createTree(int n); 10 TNode *insert(TNode *T, int v); 11 bool isSameTree(TNode *T, int n); 12 bool judge(TNode *T, int v); 13 void reset(TNode *T); 14 void freeTree(TNode *T); 15 16 17 int main() { 18 int n; 19 scanf("%d", &n); 20 21 while (n) { 22 int m; 23 scanf("%d", &m); 24 25 TNode *T = createTree(n); 26 27 while (m--) { 28 printf("%s\n", isSameTree(T, n) ? "Yes" : "No"); 29 reset(T); // 当与一个序列比较完后,别忘了重新把标识域赋值为false,再与下一组序列作比较 30 } 31 32 freeTree(T); 33 34 scanf("%d", &n); 35 } 36 37 return 0; 38 } 39 40 TNode *createTree(int n) { 41 TNode *T = NULL; 42 for (int i = 0; i < n; i++) { 43 int v; 44 scanf("%d", &v); 45 46 T = insert(T, v); 47 } 48 49 return T; 50 } 51 52 TNode *insert(TNode *T, int v) { 53 if (T == NULL) { 54 T = new TNode; 55 T->data = v; 56 T->left = T->right = NULL; 57 T->flag = false; // 创建节点的时候同时把标识域赋值为false 58 } 59 else { 60 if (v < T->data) T->left = insert(T->left, v); 61 else T->right = insert(T->right, v); 62 } 63 64 return T; 65 } 66 67 bool isSameTree(TNode *T, int n) { 68 bool ret = true; // 用来记录上一个元素比较的结果 69 for(int i = 0; i < n; i++) { // 序列中的每一个节点都要走一遍T1 70 int v; 71 scanf("%d", &v); 72 if (ret) ret = judge(T, v); // 如果ret == true,那么下一个元素还要继续判断。如果在比较中已经发现两颗树不可能相同,那么就之后的元素都不需要再走一遍T1了,但之后的元素还是要输入 73 } 74 75 return ret; 76 } 77 78 bool judge(TNode *T, int v) { 79 bool ret; 80 if (T->flag) { // 如果该节点之前被访问过 81 if (v < T->data) ret = judge(T->left, v); // 如果元素的值比根节点的值小,则走左子树 82 else ret = judge(T->right, v); // 如果元素的值比根节点的值大,则走右子树 83 } 84 else { // 如果该节点之前没有访问过 85 if (v == T->data) { // 但根节点的值与元素的值相同 86 T->flag = true; // 则flag赋值为true表示该节点被访问过了 87 ret = true; 88 } 89 else { // 该节点没有被访问过且根节点的值与元素的值不相同 90 ret = false; // 返回false表面这两个序列不可能构成同一颗BST 91 } 92 } 93 94 return ret; 95 } 96 97 void reset(TNode *T) { // 先序遍历,把T1的每一个节点的标识域赋值为false 98 if (T) { 99 T->flag = false; // 先把根节点的标识域赋值为false 100 reset(T->left); // 再把左子树的每一个节点的标识域赋值为false 101 reset(T->right); // 再把右子树的每一个节点的标识域赋值为false 102 } 103 } 104 105 void freeTree(TNode *T) { 106 if (T) { 107 freeTree(T->left); 108 freeTree(T->right); 109 delete T; 110 } 111 }
参考资料
浙江大学——数据结构:https://www.icourse163.org/course/ZJU-93001?tid=1461682474
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/14588135.html