-节点的度:一个节点含有的子树的个数称为该节点的度;
-叶节点或终端节点:度为0的节点称为叶节点;
-非终端节点或分支节点:度不为0的节点;
-父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
-子节点:一个节点含有的子树的根节点称为该节点的子节点;
-兄弟节点:具有相同父节点的节点互称为兄弟节点;
-树的度:一棵树中,最大的节点的度称为树的度;
-节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
-树的高度或深度:树中节点的最大层次;
-森林:由m(m>0)棵互不相交的多颗树的集合称为森林;
树的高度默认从1开始,是为了便于将空树高度以0表示。与之相对的,数组下标从0开始是为了方便数组名代表首元素,且能直接使用数组名加减数字找到目标元素位置。
最优一般树结构(孩子兄弟表示法):
只用两个指针表示任意多个子节点
typedef int DataType;
struct Node{
struct Node* firstChild;//指向当前节点的第一个孩子
struct Node* nextBrother;//指向当前节点“右边”的兄弟
DataType data;
};
0.一棵二叉树是结点的一个有限集合,该集合由一个根节点加上两棵别称为左子树和右子树的二叉树组成。空树也是一种子树。
1.每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
2.二叉树的子树有左右之分,其子树的次序不能颠倒。
满二叉树:
-每一个层的结点数都达到最大值的二叉树。
-层数为k且结点总数为(2^k) -1的二叉树是满二叉树。
完全二叉树:
-对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一 一对应时称之为完全二叉树。
-完全二叉树的叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
(也就是说除最后一层外都是满的。最后一层的叶节点从左到右连续存在,空缺只能存在于最右侧)
-满二叉树是特殊的完全二叉树。
1.若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2.若规定根节点的层数为1,则深度为h的二叉树的最大结点数是(2^h)-1.
3.对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0=n2+1
4.若规定根节点的层数为1,则具有n个结点的满二叉树的深度为:
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
(1)若i>0,i位置节点的双亲序号:(i-1)/2;若i=0,i为根节点编号,无双亲节点
(2)若2i+1
(3)若2i+2
1.顺序存储
-使用数组来存储。一般使用数组只适合表示完全二叉树,因为若不是完全二叉树会有空间浪费。
-实际应用中只有堆(是堆结构,不是内存堆区)才会使用数组来存储。
-顺序存储的二叉树在物理上是一个数组,在逻辑上是一颗二叉树。
2.链式存储
-用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
-通常的实现方式是链表中每个结点由三个域组成,数据域和左右指针域。左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
-链式结构又分为二叉链和三叉链,暂且只讨论二叉链。
已知:二叉树每个节点可以看作一个根,每个根有两个子树。空子树也算子树,只不过空树没有子树。
2.中序遍历/中根遍历:左子树、根、右子树
3.后序遍历/后根遍历:左子树、右子树、根
非递归实现:
4.层序遍历:用队列实现,从第一层开始,每层从左到右,逐层遍历
堆结构是一种用数组实现的完全二叉树。也可以用链式结构实现,但远不如数组方便。
大堆(大根堆):树中所有亲节点的值都大于等于其子节点的值。
小堆(小根堆):树中所有亲节点的值都小于等于其子节点的值。
任意数组都可以看作是完全二叉树,但未必能看作是堆结构。
根据1.2.3,堆结构使用数组以实现顺序存储。
根据1.2.2-5,关于各节点在数组中的下标,有:
parent = (child - 1) / 2
#pragma once
#include
#include
#include
#include
#include
typedef int HPDataType;
typedef struct Heap{
HPDataType* arr;//保存元素的数组
int size;//数组所存元素的数量
int capacity;//可用容量
}HP;
void HeapPrint(HP* php);//打印数组
void Swap(HPDataType* p1,HPDataType* p2);//交换数组中的元素
void AdjustUp(HPDataType* arr, int child);//向上调整子节点,这里用大堆逻辑
void AdjustDown(HPDataType* arr,int size,int parent);//向下调整亲节点,这里用大堆逻辑
void HeapInit(HP* php);//初始化堆
void HeapDestroy(HP* php);//销毁堆
void HeapPush(HP* php,HPDataType x);//元素入堆
void HeapPop(HP* php);//根元素出堆
void HeapCreate(HP* php, HPDataType* a, int size);//根据数组建堆
HPDataType HeapTop(HP* php);//取顶
int HeapSize(HP* php);//查大小
bool HeapEmpty(HP* php);//判空
void HeapSort(int* arr, int size);//堆排序
#include"Heap.h"
//打印数组
void HeapPrint(HP* php) {
assert(php);
for (int i = 0; i < php->size; i++) {
printf("%d ", php->arr[i]);
}
printf("\n");
}
//交换数组中的元素
void Swap(HPDataType* p1, HPDataType* p2) {
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整子节点,这里用大堆逻辑
void AdjustUp(HPDataType* arr, int child) {
int parent = (child - 1) / 2;//按公式求出初始亲节点下标
while (child > 0){//从下至上(从叶至根)逐个调整,遇根则停止循环
if (arr[child] > arr[parent]) {//这里的>换成<即为小堆逻辑
Swap(&arr[child], &arr[parent]);//符合交换的条件则交换亲子节点(的数据)
child = parent;//在交换后保持该下标指向待调整的节点(数据)
parent = (child - 1) / 2;//按公式求出下一个要比较的亲节点的下标
}
else{
break;
}
}
//这里的节点不是链表节点,而是逻辑上的二叉树的节点
}
//向下调整亲节点,这里用大堆逻辑
void AdjustDown(HPDataType* arr, int size, int parent) {
int child = parent * 2 + 1;//先默认左子节点较大
while (child<size){//完全二叉树,若左子节点不存在则右子节点必不存在
//检查两子节点大小关系
if (child + 1 < size && arr[child + 1] > arr[child]) {//这里的>换成<即为小堆逻辑
//if右子节点存在且比左子节点大(若不检查存在则可能越界)
child++;//将child指向右子节点
}
//检查子节点是否大于亲节点
if (arr[child] > arr[parent]) {//这里的>换成<即为小堆逻辑
Swap(&arr[child], &arr[parent]);//符合交换的条件则交换亲子节点(的数据)
parent = child;//在交换后保持该下标指向待调整的节点(数据)
child = parent * 2 + 1;//按公式求出下一个要比较的子节点的下标
}
else{
break;//子小于等于亲,则跳出
}
}
}
//初始化堆
void HeapInit(HP* php) {
assert(php);
php->arr = NULL;//初始不开空间,故arr初始指向空
php->size = php->capacity = 0;//初始大小与容量为0
}
//销毁堆
void HeapDestroy(HP* php) {
assert(php);
free(php->arr);//释放arr
php->arr = NULL;//置空arr
php->size = php->capacity = 0;//大小与容量归零
}
//元素入堆
void HeapPush(HP* php, HPDataType x) {
assert(php);
if (php->size == php->capacity) {//检查扩容
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;//若尚未开空间则开4个元素大小,若容量不足则默认扩到之前大小的两倍
HPDataType* tmp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * newCapacity);//申请空间
if (tmp == NULL) {//检查空间申请结果
perror("realloc failed");
exit(-1);
}
php->arr = tmp;//交付扩容成果
php->capacity = newCapacity;//更新可用容量
}
php->arr[php->size] = x;//在数组尾部插入元素。size作下标时对应最后一个元素的下一位
php->size++;//size增加1,下标size指向新的尾部元素的下一位
AdjustUp(php->arr, php->size-1);//从size-1,即最后一个元素开始,向上调整
}
//根元素出堆
void HeapPop(HP* php) {
assert(php);
assert(php->size > 0);
Swap(&php->arr[0], &php->arr[php->size - 1]);//将根元素与数组尾部元素交换,以便删除
php->size--;//删除交换后的尾部元素,也就是原本的根部元素
AdjustDown(php->arr, php->size, 0);//向下调整
}
//根据数组建堆
void HeapCreate(HP* php, HPDataType* a, int size) {
assert(php);//确保堆结构可用
//这里不直接复用push建堆,因为那样效率不高(N*logN)
php->arr = (HPDataType*)malloc(sizeof(HPDataType) * size);//按照传的数组的大小开空间给堆结构的数组。因为是从头建堆所以不realloc
if (php->arr == NULL) {//检查开空间的结果
perror("malloc failed");
exit(-1);
}
memcpy(php->arr, a, sizeof(HPDataType) * size);//将传的数组的内容拷贝到堆结构的数组
php->size = php->capacity = size;//设置堆结构中的容量与元素数量
for (int i = (size - 1 - 1) / 2; i >= 0; i--) {//建堆算法:从最后一个节点的亲节点开始,直到根节点,依次执行向下调整
AdjustDown(php->arr, size,i);
}//该方法也用于堆排序
}
//取顶
HPDataType HeapTop(HP* php) {
assert(php);
assert(php->size > 0);
return php->arr[0];//直接返回根节点,即堆顶的数据,
}
//查大小
int HeapSize(HP* php) {
assert(php);
return php->size;//直接返回size
}
//判空
bool HeapEmpty(HP* php) {
assert(php);
return php->size == 0;//zize为0则为空
}
//堆排序:直接将数组看作完全二叉树,再按照堆结构的规则向上或向下调整,从而排序。并不需要另外创建一个堆结构供排序用。
void HeapSort(int* arr, int size) {
/*//向上调整建堆(O(N*logN),效率比向下调更低)
for (int i = 1; i < size; i++) {
AdjustUp(a, i);
}*/
//向下调整建堆(从倒数第二层的最后一个节点的亲节点开始,直到根节点依次调整)
//要排升序,则建大堆(反之,若排降序则要建小堆)
for (int i = ((size-1)-1)/2; i >= 0; i--) {
AdjustDown(arr, size, i);
}//O(N)
//先将值最大的元素与最后一个元素(第size个元素)交换,使得最大的元素位于最后(成为第size个),
//,再以前size-1个元素为堆进行向下调整,选出次大的,
//,以同样的方式逐次减小要调整的堆,直至整个数组调整完毕。
int end = size - 1;
while (end > 0){
Swap(&arr[0], &arr[end]);
AdjustDown(arr, end, 0);
end--;
}//O(N*logN)
}//堆排序效率:O(N+N*logN) -> O(N*logN)
#include
#include
#include"Queue.h"
typedef char BTDataType;
typedef struct BinaryTreeNode {
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
BTNode* BuyBTNode(BTDataType x) {
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL) {
perror("malloc failed");
exit(-1);
}
node->_data = x;
node->_left = node->_right = NULL;
return node;
}
//前序遍历字符串构建二叉树,#表示空
BTNode* BinaryTreeCreate(BTDataType* str, int* pi) {
//i传地址,以保证++的是同一个i
//遇#返回空
if (str[*pi] == '#') {
(*pi)++;
return NULL;
}
//非#创建新节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL) {
printf("malloc failed\n");
exit(-1);
}
root->_data = str[(*pi)++];
root->_left = BinaryTreeCreate(str, pi);
root->_right = BinaryTreeCreate(str, pi);
return root;
}
//递归前序遍历
void PrevOrder(BTNode* root) {
//空树作为终止条件
if (root == NULL) {
printf("NULL ");
return;//返回到发起本次调用的栈帧,也就是递归的上一层。
//调用结束的栈帧会被销毁
}
//前序:根、左子树、右子树
//若非空,则访问当前节点
printf("%c ", root->_data);
//先从左子树的根向下递归,然后才是右子树
PrevOrder(root->_left);
PrevOrder(root->_right);
//从右子树的递归调用的栈帧返回本次调用的栈帧后,本次调用结束
//返回到发起本次调用的栈帧,也就是递归的上一层。
//调用结束的栈帧会被销毁
}
//递归中序遍历
void InOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
//中序:左子树、根、右子树
InOrder(root->_left);
printf("%c ", root->_data);
InOrder(root->_right);
}
//递归后序遍历
void PostOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
//后序:左子树、右子树、根
PostOrder(root->_left);
PostOrder(root->_right);
printf("%c ", root->_data);
}
//层序遍历-使用队列,一层带一层直至队列pop到空
void LevelOrder(BTNode* root) {
Queue q;
QueueInit(&q);
if (root) {
QueuePush(&q, root);//树根的地址入队
}
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);//队头地址保存到front
printf("%c ", front->_data);//打印队头
QueuePop(&q);//队头出队
//通过front判断原队头是否存在子节点,存在则将子节点依次入队排在后面
if (front->_left) {
QueuePush(&q, front->_left);
}
if (front->_right) {
QueuePush(&q, front->_right);
}
}
printf("\n");
QueueDestroy(&q);
}
//二叉树销毁:使用后序遍历(最优)
void TreeDestroy(BTNode* root) {
if (root == NULL) {
return;
}
TreeDestroy(root->_left);
TreeDestroy(root->_right);
free(root);
//这个free可以不用跟置空,因为是形参
//由于没用二级指针,树指针的置空操作需要由调用者进行
}
//求二叉树节点个数
int TreeSize(BTNode* root) {
return root == NULL ? 0 :
TreeSize(root->_left) + TreeSize(root->_right) + 1;
}
//求二叉树叶节点个数
int TreeLeafSize(BTNode* root) {
if (root == NULL) {
return 0;
}
if (root->_left == NULL && root->_right == NULL) {
return 1;
}
return TreeLeafSize(root->_left) + TreeLeafSize(root->_right);
}
//求树的高度/深度
int TreeHeight(BTNode* root) {
if (root == NULL) {
return 0;
}
int leftHeight = TreeHeight(root->_left);
int rightHeight = TreeHeight(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
//求第k层的节点个数,k>=1
int TreeKLevelSize(BTNode* root, BTDataType k) {
if (root == NULL) {
return 0;
}
if (k == 1) {
return 1;
}
return TreeKLevelSize(root->_left, k - 1) + TreeKLevelSize(root->_right, k - 1);
}
//查找值为x的节点,这里的x为字符
BTNode* TreeFind(BTNode* root, BTDataType x) {
if (root == NULL) {
return NULL;
}
if (root->_data == x) {
return root;
}
//每层递归的返回值都只会返回给上一层,要确保该值被上一层接收且能够继续向上传递
BTNode* ret1 = TreeFind(root->_left, x);
if (ret1) {
return ret1;
}
BTNode* ret2 = TreeFind(root->_right, x);
if (ret2) {
return ret2;
}
return NULL;
}
//判断完全二叉树:使用层序遍历思路
//相比前面的层序遍历,空节点也入队,便能在空节点出队时看后续是否为全空队列来判断完全二叉树
//不能通过节点数量判断,因为最后一层的节点可能不连续
bool TreeComplete(BTNode* root) {
Queue q;
QueueInit(&q);
if (root) {
QueuePush(&q, root);
}
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL) {
break;//遇空则跳出,开始检查
}
else {//未遇空则继续层序遍历
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
}
//遇空后检查队列中后续节点是否全空,全空则为完全二叉树
while (!QueueEmpty(&q)) {
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL) {
QueueDestroy(&q);
return false;//空后有非空则该树不是完全二叉树
}
}
QueueDestroy(&q);//即便到这一步队列后续必为空,也不省略destroy,
//,这样能确保即便是带哨兵位的队列也不会内存泄漏,使得本函数不受队列的实现方式影响
return true;
}
int main() {
//手动创建节并链接以构建树
BTNode* n1 = BuyBTNode('1');
BTNode* n2 = BuyBTNode('2');
BTNode* n3 = BuyBTNode('3');
BTNode* n4 = BuyBTNode('4');
BTNode* n5 = BuyBTNode('5');
BTNode* n6 = BuyBTNode('6');
BTNode* n7 = BuyBTNode('7');
n1->_left = n2;
n1->_right = n4;
n2->_left = n3;
n2->_right = n7;
n4->_left = n5;
n4->_right = n6;
PrevOrder(n1);
printf("\n");
InOrder(n1);
printf("\n");
PostOrder(n1);
printf("\n");
LevelOrder(n1);
printf("\n");
printf("TreeSize:%d\n", TreeSize(n1));
printf("TreeLeafSize:%d\n", TreeLeafSize(n1));
printf("TreeHeight:%d\n", TreeHeight(n1));
printf("Tree2LevelSize:%d\n", TreeKLevelSize(n1, 2));
printf("Tree3LevelSize:%d\n", TreeKLevelSize(n1, 3));
printf("Tree4LevelSize:%d\n", TreeKLevelSize(n1, 4));
printf("TreeFind '4':%d\n", (int)TreeFind(n1, '2'));
printf("TreeComplete:%d\n", TreeComplete(n1));
TreeDestroy(n1);
n1 = NULL;
return 0;
}
之前博文中实现的队列需要稍作调整(改变元素类型)才能用在这里。
#pragma once
#include
#include
#include
#include
struct BinartTreeNode;//前置声明以确保Queue.h在Queue.c展开也能正常编译。也可以直接把二叉树节点的定义复制过来。
typedef struct BinartTreeNode* QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue {//要同时控制多个值,使用结构会比较方便(也是代替二级指针的一种方案)
QNode* head;//队头
QNode* tail;//队尾
int size;//队内节点数
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq,QDataType x);//队列的出入是单向的,所以不会同时存在头插和尾插
void QueuePop(Queue* pq);//与插入同理,头删和尾删只能二选一
//本项目使用头删尾插
//即便是简单的功能也应做成函数供调用,方便维护,降低耦合,
//,而不应令用户在使用功能时必须关注具体的实现方式,即便用户是自己
QDataType QueueFront(Queue* pq);//取队头节点存放的值
QDataType QueueBack(Queue* pq);//取队尾节点存放的值
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);
#include"Queue.h"
void QueueInit(Queue* pq) {
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq) {
assert(pq);
QNode* cur = pq->head;
while (cur){
QNode* del = cur;
cur = cur->next;//记住下一位的位置
free(del);
//这里不需要del=NULL,因为局部变量出当前函数就自动销毁,不会在作为野指针的情况下被意外访问
}
pq->head = pq->tail = NULL;//这个需要置空,因为若作为野指针存在有可能被意外访问
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x) {
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));//不需要专门写BuyNode函数,因为这里只有这个Push函数需要开节点
if (newnode == NULL) {
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL) {//队内尚无节点的情况
pq->head = pq->tail = newnode;
}
else{//队内已有节点的情况
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL) {//若队内只有一个节点,即head==tail时
free(pq->head);
pq->head = pq->tail = NULL;//此时head与tail都需要置空
}
else{//若队内不止一个节点
QNode* del = pq->head;
pq->head = pq->head->next;//尾节点的next为空,所以连续pop时head最后会自动置空
free(del);
//该部分只能处理head
//因此,若只有该部分而不对队内只有一个节点的情况单独处理,
//,则当只剩一个节点时,会在head置空并释放del后使tail成为野指针
}
//相对于这里的把只剩一个节点的情况单独拎出来处理的方式,
//另一种处理只剩一个节点时tail的置空问题的方式是,
//在最后检查head是否为空,若head为空则tail也置空
pq->size--;
}
QDataType QueueFront(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
bool QueueEmpty(Queue* pq) {
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
int QueueSize(Queue* pq) {
assert(pq);
return pq->size;
}
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:
//递归判断每个子树的根与左右子节点是否相等,遇到空树直接返回
//先判断当前子树,再向下递归
bool isUnivalTree(struct TreeNode* root){
if(root==NULL){
return true;
}
if(root->left && root->left->val != root->val){
return false;
}
if(root->right && root->right->val != root->val){
return false;
}
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:同时遍历并比较
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
//当前节点都为空则同
if(p==NULL && q==NULL){
return true;
}
//已判断当前节点并不都为空,故其中一个为空则异
if(p==NULL || q==NULL){
return false;
}
//至此,当前节点都不为空,遂判断值是否相异
if(p->val != q->val){
return false;
}
//同时递归遍历比较
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:
//复用isSameTree函数来比较每一棵子树是否相同
//每个节点都可以看作一棵子树的根
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p==NULL && q==NULL){
return true;
}
if(p==NULL || q==NULL){
return false;
}
if(p->val != q->val){
return false;
}
return isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
if(root == NULL){
return false;
}
if(isSameTree(root,subRoot)){
return true;
}
return isSubtree(root->left,subRoot)
|| isSubtree(root->right,subRoot);
}
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
//思路:将一侧子树翻转后与另一侧子树比对(只在效果上是翻转的)
//用于比对的子函数
bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2){
if(root1 == NULL && root2 == NULL){
return true;
}
if(root1 == NULL || root2 == NULL){
return false;
}
if(root1->val != root2->val){
return false;
}
return _isSymmetric(root1->left,root2->right)
&& _isSymmetric(root1->right,root2->left);
}
bool isSymmetric(struct TreeNode* root){
return !root || _isSymmetric(root->left,root->right);
}