整理思路
因为整个算法是要实现比较二叉树的结构与结点值,那么就有四种情况会产生:1.两个二叉树完全相同;2.两个二叉树只有结构不同;3.两个二叉树只有结点值不同;4.两个二叉树结构与结点值都不同。具体4种情况如下图所示:
情况1:两个二叉树相同
情况2:二叉树结构不同
情况3:只有结点值不同
情况4:结构与结点值都不同
因为需要比较每个结点的左右子树,所以这里采用递归的方式进行编写算法。
整体思路为以下三步:
1.创建二叉树;
2.遍历二叉树(用于检查二叉树,可省略)
3.比较两棵二叉树
二叉树创建
首先需要创建二叉树结构体:
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
二叉树的创建类似于递归前序遍历,先创建结点值,接着递归调用结点的左子树与右子树。
因为数据域采用的int类型的值,所以这里规定若输入9999,那么该结点为NULL。
void creatTree (BiTree *T) {
int tData; //输入的结点值
cout<<"please enter the number that you want to insert. 9999 is NULL:"<<endl;
cin>>tData;
if (tData == 9999) {
(*T) = NULL;
} else {
(*T) = (BiTree)malloc(sizeof(BiTree)); //为结点分配内存空间
(*T)->data = tData;
creatTree(&(*T)->lchild);
creatTree(&(*T)->rchild);
}
}
二叉树遍历
遍历则是为了更好地检查自己创建的二叉树,所以这里只采用了递归先序遍历。
void preOrder (BiTree T) {
if (T != NULL) {
visit(T);
preOrder(T->lchild);
preOrder(T->rchild);
}
}
相应的visit()方法:
void visit (BiTree T) {
cout<<T->data<<" ";
}
二叉树比较
因为考虑到该算法不仅要比较二叉树的结构,同时还有结点值,那么就使用4个状态值表达两个二叉树的情况:1(二叉树相同);2(结构不同);3(结点值不同);4(结构与结点值都不同)。
二叉树的比较的逻辑为:
1.先判断结点是否都为NULL,若都为NULL,那么这里的结构是没有问题的。也可以说判断这两棵树是否都为空树,只是这里的空树指的是以该结点为起点的树,即该结点与其下面的所有子节点构成的一棵树。如图所示,假设红色箭头是递归到的位置,那么该结点就为一棵空树:
2.接着判断二叉树的结构是否相同,若结构都不同了,那么就没有必要再比较这棵树(或子树)的值了。
3.若二叉树不为空,且结构相同,那么就比较二叉树的结点值。
因为在递归左右子树后需要返回正确的状态码,那么需要考虑以下4种情况:
- 左右子树返回值完全相同;
- 左右子树中至少有一棵子树结构与结点值都不同;
- 左右子树中,一棵子树结构不同,另一棵子树结点值不同;
- 左右子树中,一棵子树完全相同,另一棵有问题;
针对以上4种情况处理的方式为:
- 返回左右子树任意一棵树的返回值;
- 因为某一棵子树已经完全不同了,那么整棵树都是完全不同的,所以返回4;
- 因为一棵结构不同,一棵值不同,那么合在一起就是结构也不同,值也不同,所以返回4;
- 因为有一棵树有问题,那么整棵树都有问题,而且该问题就是出问题的那棵树的问题。
但是这里不能盲目的书写if语句,因为需要考虑到相应的事件出现的概率,从而减少在if语句中花费的时间,提高程序执行的效率。所以可以作出一个矩阵进行相应的分析,如图所示,前一个数字代表左子树返回的情况,后一个数字代表右子树返回的情况:
那么从矩阵中可以分析得到:
- 两个子树情况相同:一共有4种可能,即11、22、33、44,出现的概率为1/4;
- 左右子树中,一棵子树完全相同,另一棵有问题:一共有7种可能,即11、12、13、14、21、31、41,出现的概率为7/16;
- 左右子树中至少有一棵子树结构与结点值都不同:一共有7种可能,即14、24、34、44、41、42、43,出现的概率为7/16;
- 左右子树中,一棵子树结构不同,另一棵子树结点值不同:一共有2种可能,即23、32,出现的概率为1/8;
因为2与3概率相同,所以无论谁在前后都一样,整体代码如下:
int BiTreeContrast (BiTree T1, BiTree T2) {
//判断结构: 是否为NULL, 若是, 则结构相同
if (T1 == NULL && T2 == NULL) {
return 1;
}
//判断结构: 是否一个为NULL, 另一个不为NULL, 若是, 则结构不同
//因为上面已经判断过了两个都为NULL的情况, 所以这里无需考虑两个都为NULL的情况
else if (T1 == NULL || T2 == NULL) {
return 2;
}
//判断结构: 两个都不为NULL
else {
//判断结点值: 若两个相同, 则递归调用左右孩子, 之后返回1
if (T1->data == T2->data) {
int leftCompare = BiTreeContrast(T1->lchild, T2->lchild); //接受递归左子树的值
int rightCompare = BiTreeContrast(T1->rchild, T2->rchild); //接受递归右子树的值
//只要有一个子树为4, 那么整棵树都为4
if (leftCompare == 4 || rightCompare == 4) {
return 4;
}
//一棵子树完全相同, 另外一棵子树结构或结点值不同, 则返回该子树的值
else if ((leftCompare == 1 && rightCompare != 1) || (leftCompare != 1 && rightCompare == 1)) {
int max;
//将大的值赋值给max, 考虑两值相等的情况, 则无需再走else
if (leftCompare >= rightCompare) {
max = leftCompare;
} else {
max = rightCompare;
}
return max;
}
//左右结点返回的情况都完全相同
else if (leftCompare == rightCompare) {
return leftCompare;
}
//左右子树某棵结构不同, 另外一棵结点值不同
else if ((leftCompare == 2 && rightCompare == 3) || (leftCompare == 3 && rightCompare == 2)) {
return 4;
}
}
return 3;
}
}
总代码
总体代码如下:
#include <iostream>
using namespace std;
typedef struct BiTNode {
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
/**
* 递归构造树:
* 1.类比先序遍历, 输入结点值: 输入9999则该结点为NULL
* 输入其余数值则为该结点值
* 2.若输入非9999, 则递归访问其左结点, 再递归访问其右结点
*
* param: T (需要创建的树)
*/
void creatTree (BiTree *T) {
int tData; //输入的结点值
cout<<"please enter the number that you want to insert. 9999 is NULL:"<<endl;
cin>>tData;
if (tData == 9999) {
(*T) = NULL;
} else {
(*T) = (BiTree)malloc(sizeof(BiTree)); //为结点分配内存空间
(*T)->data = tData;
creatTree(&(*T)->lchild);
creatTree(&(*T)->rchild);
}
}
/**
* 访问结点值
* param: T (需要遍历的结点)
*/
void visit (BiTree T) {
cout<<T->data<<" ";
}
/**
* 先序遍历:
* 1.判断结点值是否为空;
* 2.若不为空, 则打印结点值, 递归调用左孩子, 递归调用右孩子
*
* param: T (需要遍历的结点)
*/
void preOrder (BiTree T) {
if (T != NULL) {
visit(T);
preOrder(T->lchild);
preOrder(T->rchild);
}
}
/**
* 比较两棵二叉树结构与结点值
*
* param: T1 (比较的第一棵树), T2 (比较的第二棵树)
* return: 1 (完全相同), 2 (结构不同), 3 (结点值不同), 4 (结点值与结构都不同)
*/
int BiTreeContrast (BiTree T1, BiTree T2) {
//判断结构: 是否为NULL, 若是, 则结构相同
if (T1 == NULL && T2 == NULL) {
return 1;
}
//判断结构: 是否一个为NULL, 另一个不为NULL, 若是, 则结构不同
//因为上面已经判断过了两个都为NULL的情况, 所以这里无需考虑两个都为NULL的情况
else if (T1 == NULL || T2 == NULL) {
return 2;
}
//判断结构: 两个都不为NULL
else {
//判断结点值: 若两个相同, 则递归调用左右孩子, 之后返回1
if (T1->data == T2->data) {
int leftCompare = BiTreeContrast(T1->lchild, T2->lchild); //接受递归左子树的值
int rightCompare = BiTreeContrast(T1->rchild, T2->rchild); //接受递归右子树的值
//只要有一个子树为4, 那么整棵树都为4
if (leftCompare == 4 || rightCompare == 4) {
return 4;
}
//一棵子树完全相同, 另外一棵子树结构或结点值不同, 则返回该子树的值
else if ((leftCompare == 1 && rightCompare != 1) || (leftCompare != 1 && rightCompare == 1)) {
int max;
//将大的值赋值给max, 考虑两值相等的情况, 则无需再走else
if (leftCompare >= rightCompare) {
max = leftCompare;
} else {
max = rightCompare;
}
return max;
}
//左右结点返回的情况都完全相同
else if (leftCompare == rightCompare) {
return leftCompare;
}
//左右子树某棵结构不同, 另外一棵结点值不同
else if ((leftCompare == 2 && rightCompare == 3) || (leftCompare == 3 && rightCompare == 2)) {
return 4;
}
}
return 3;
}
}
int main () {
BiTree T1, T2;
cout<<"please construct the first tree:"<<endl;
creatTree(&T1);
cout<<"please construct the second tree:"<<endl;
creatTree(&T2);
preOrder(T1);
cout<<endl;
preOrder(T2);
cout<<endl;
switch (BiTreeContrast(T1, T2))
{
case 1:
cout<<"These trees are completely the same!"<<endl;
break;
case 2:
cout<<"These trees have different structures!"<<endl;
break;
case 3:
cout<<"These trees have different data!"<<endl;
break;
case 4:
cout<<"These trees have neither same structure nor the data!"<<endl;
break;
default:
break;
}
system("pause");
return 0;
}