前面一些是基本概念和算法题,后面有一些是leetcode上的,边练边记录。
1.二叉树的基本概念
1)二叉树性质
- 性质1:第i层上的节点个数<= 2^(i-1),其中i>=1。
- 性质2:深度为k的二叉树的节点个数<= 2^k - 1,其中k>=1。
- 性质3:节点总数为n的二叉树,高度>=log2(n+1)。
2)满二叉树
非叶子节点均有左右两个孩子(度为2);叶子节点均在同一层
- 每个节点的左右子树的高度差为0。
- 节点总个数n,高度h=log2(n+1);当前层为i,当前层节点为2^(i-1),其中(i >= 1)。
- 时间复杂度O(h) = O(log2(n)),与h(高度)有关。
3)完全二叉树
添加节点的顺序,从上至下,从左向右。
- 如果二叉树的高度为h,除了第h层外,其它的h-1层,每一层的节点数都达到最大数,而第h层的节点个数可以不用达到最大,但必须都全部集中在左边。(叶子节点全部集中在最下层和次下层)
- 孩子节点(序号)/2 = 父结点(序号)
4)平衡二叉树
平衡二叉树的每个节点的左右子树高度差 最多为1
2. 深度遍历(先序+中序+后序)
1)递归实现
//递归先序遍历
void preOrderRec(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return;
}
cout << rootNode->data << " "; //根
preOrderRec(rootNode->Lchild); //左
preOrderRec(rootNode->Rchild); //右
}
//递归中序遍历
void midOrderRec(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return;
}
midOrderRec(rootNode->Lchild); //左
cout << rootNode->data << " "; //根
midOrderRec(rootNode->Rchild); //有
}
//递归后序遍历
void lastOrderRec(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return;
}
lastOrderRec(rootNode->Lchild); //左
lastOrderRec(rootNode->Rchild); //右
cout << rootNode->data << " "; //根
}
2)非递归实现
用堆栈实现(先进后出)
采用了C++的STL标准模板库,需要包含#include<stack>头文件
格式:<vector><类型>变量名
例如:
vector<int> a;就定义了一个容器对象 a,a 代表一个长度可变的数组,
数组中的每个元素都是 int 类型的变量;
vector<double> b,定义了另一个容器对象 b 类型double;
- 非递归先序
//非递归先序遍历
void depthSearch(struct treeNode *rootNode)
{
stack<treeNode*> nodeStack; //包含#include<stack>头文件 stack<类型>别名
nodeStack.push(rootNode); //往栈中添加根节点
treeNode* curNode; //定义一个节点
while (!nodeStack.empty())
{
curNode = nodeStack.top(); //将栈顶的节点赋值给curNode
cout << curNode->data << " "; //输出根节点
nodeStack.pop(); //将当前节点出栈
if (curNode->Rchild) //先判断右孩子并先进栈(因为先进后出)
{
nodeStack.push(curNode->Rchild);
}
if (curNode->Lchild) //左孩子后进栈,因为需要下一次先出
{
nodeStack.push(curNode->Lchild);
}
}
}
- 非递归中序
需要一个堆栈,先将根节点的左孩子全部压栈,再依次出栈,再处理出栈的当前节点的右孩子
代码图解
void midOrder(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return;
}
stack<treeNode*>nodeStack; //创建堆栈
struct treeNode* curNode = rootNode;
while (curNode || !nodeStack.empty())
{
while (curNode != NULL) //先将当前节点的左孩子压栈
{
nodeStack.push(curNode);
curNode = curNode->Lchild;
}
curNode = nodeStack.top();
nodeStack.pop(); //一弹
cout << curNode->data << " "; //二印
curNode = curNode->Rchild; //三孩子
}
}
- 非递归后序
需要创建两个堆栈,一个存“根”,另一个存结果
对非递归后序遍历代码图解
1)第一个while循环 ,根转移,加孩子
2)加到该节点没有孩子为止
3)重复根转移+加孩子,直到stack1没有“根”可以转移,为空
4)最后堆栈2依次弹出节点
代码:
void lastOrder(struct treeNode* rootNode)
{
stack<treeNode*>treeStack1;
stack<treeNode*>treeStack2; //设立两个堆栈
treeStack1.push(rootNode);
while (!treeStack1.empty()) //如果treeStack1不为空
{
treeNode* temp = treeStack1.top();
treeStack1.pop();
treeStack2.push(temp); //根转移(出1进2)
if (temp->Lchild)
{
treeStack1.push(temp->Lchild);
}
if (temp->Rchild)
{
treeStack1.push(temp->Rchild); //加孩子
}
}
while (!treeStack2.empty()) //将treestack2中依次出栈,就是最终结果
{
treeNode* curNode = treeStack2.top();
treeStack2.pop();
cout << curNode->data << " " ;
}
}
3. 广度优先遍历(层次优先遍历)
分层遍历,从上到下,从左到右。
利用队列,一头出节点,另一头进该节点的左右孩子,直至当前节点没有孩子可进,而另一头一直在出节点,一直到队列为空。
采用队列实现(先进先出)
需要包含头文件#include<queue>
顺序容器常用的成员函数:
front():返回容器中第一个元素的引用。
back():返回容器中最后一个元素的引用。
push_back():在容器末尾增加新元素。
pop_back():删除容器末尾的元素。
insert(...):插入一个或多个元素
。。。。。。
广度优先遍历的读取结果如下图所示
void breadthSearch(struct treeNode* rootNode)
{
queue<struct treeNode*>nodeQueue;
nodeQueue.push(rootNode);
while (!nodeQueue.empty())
{
struct treeNode* curNode = nodeQueue.front(); //顺序容器,返回容器中的第一个元素
nodeQueue.pop();
cout << curNode->data << " ";
if (curNode->Lchild)
{
nodeQueue.push(curNode->Lchild); //此处左孩子先入队(先进先出)
}
if (curNode->Rchild)
{
nodeQueue.push(curNode->Rchild); //右孩子后入队
}
}
}
4. 节点的构建以及主函数测试部分如下
#include<iostream>
#include<string>
using namespace std;
//构造节点类型
struct treeNode //没有返回值
{
char data; //数据域
struct treeNode *Lchild; //指针域 左右孩子
struct treeNode *Rchild;
};
//创造节点
struct treeNode* createNode(char data) //因为返回值类型是节点,所以用struct treeNode*
{
//为根节点开辟动态空间
struct treeNode* newNode = (struct treeNode*)malloc(sizeof(struct treeNode));
newNode->data = data;
newNode->Lchild = NULL;
newNode->Rchild = NULL;
return newNode;
}
//链接节点
void insertNode(struct treeNode* newNode, struct treeNode* LchildNode, struct treeNode* RchildNode)
{
newNode->Lchild = LchildNode;
newNode->Rchild = RchildNode;
}
int main()
{
//创建每个独立的节点
struct treeNode* node1 = createNode('A');
struct treeNode* node2 = createNode('B');
struct treeNode* node3 = createNode('C');
struct treeNode* node4 = createNode('D');
struct treeNode* node5 = createNode('E');
struct treeNode* node6 = createNode('F');
struct treeNode* node7 = createNode('G');
struct treeNode* node8 = createNode('H');
//链接每个节点成一棵树
insertNode(node1, node2, node3);
insertNode(node2, node4, node5);
insertNode(node3, node6, node7);
insertNode(node4, node8, NULL);
//深度遍历(只测试递归部分)
preOrderRec(node1); //直接传入节点,不用加&
cout << endl;
midOrderRec(node1);
cout << endl;
lastOrderRec(node1);
cout << endl;
system("pause");
}
测试结果
节点的创建以及主函数的测试部分还可以替换成以下形式
#include<iostream>
#include<string>
using namespace std;
//构造节点类型
struct treeNode //没有返回值,只是一个简单的结构体
{
char data; //数据域
struct treeNode *Lchild; //指针域 左右孩子
struct treeNode *Rchild;
};
int main()
{
//创建节点
treeNode node1 = { 'A', NULL, NULL };
treeNode node2 = { 'B', NULL, NULL };
treeNode node3 = { 'C', NULL, NULL };
treeNode node4 = { 'D', NULL, NULL };
treeNode node5 = { 'E', NULL, NULL };
treeNode node6 = { 'F', NULL, NULL };
treeNode node7 = { 'G', NULL, NULL };
//建立节点的关系
node1.Lchild = &node2;
node1.Rchild = &node3;
node2.Lchild = &node4;
node2.Rchild = &node5;
node3.Lchild = &node6;
node3.Rchild = &node7;
//深度遍历测试
preOrderRec(&node1); //注意加&
cout << endl;
midOrderRec(&node1);
cout << endl;
lastOrderRec(&node1);
cout << endl;
system("pause");
}
测试结果
5. 常见的二叉树算法题
大部分都是采用递归算法
1)求深度
树的深度=max(左树的深度,右树的深度)+1,而左树和右树又可以继续往下递归,直到当前节点为空。
int TreeDepth(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return 0;
}
treeNode* leftNode = rootNode->Lchild;
treeNode* rightNode = rootNode->Rchild;
return max(TreeDepth(leftNode), TreeDepth(rightNode)) + 1;
}
2)求叶子节点数量
- 二叉树为空,返回0
- 二叉树是叶子节点,返回1
- 二叉树不是叶子节点,叶子节点数 = 左树叶子 + 右树叶子
//叶子节点
int leafNum(struct treeNode* rootNode)
{
if (rootNode == NULL)
{
return 0;
}
if (rootNode->Lchild == NULL || rootNode->Rchild == NULL)
{
return 1;
}
treeNode* leftNode = rootNode->Lchild;
treeNode* rightNode = rootNode->Rchild;
return leafNum(leftNode) + leafNum(rightNode);
}
3)二叉树节点个数
总的节点个数=左树节点+右树节点
int nodeNum(treeNode* rootNode)
{
if (rootNode == NULL)
{
return 0;
}
return (nodeNum(rootNode->Lchild) + nodeNum(rootNode->Rchild) + 1);
}
4)二叉树第K层节点个数
我们需要的结果只和当前这一层的节点个数有关系,和上一层或下一层都没有关系。怎么才能知道已经遍历到指定的K层?遍历到K层,应该返回什么?
int Knum(treeNode* rootNode,int k)
{
if (rootNode == NULL || k <= 0) //如果当前这层已经为空,却还没有到达指定的K层,该层作废,不计数
{
return 0;
}
if (k == 1) //K作用:降层。当K=1时,刚好到达这一层,K每递归一次,降一层
{
return 1;
}
return (Knum(rootNode->Lchild, k-1) + Knum(rootNode->Rchild, k-1));
}
5)二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
二叉树镜像,缩小范围看,从上往下,就是交换左右子树,依次往下循环,直到该树为空。
treeNode *Mirror(struct treeNode *rootNode)
{
if (rootNode== NULL)
{
return rootNode;
}
treeNode* temp;
temp = rootNode->Lchild;
rootNode->Lchild = rootNode->Rchild;
rootNode->Rchild = temp; //交换左右孩子
Mirror(rootNode->Lchild);
Mirror(rootNode->Rchild); //并行递归
return rootNode;
}
6)对称二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
- 方法1:利用两个队列分别存储节点
queue1存储的节点顺序是广度遍历(先左后右) queue2存储的节点顺序是广度遍历的反向(先右后左)
一边存储,一边比较queue1和queue2当前值(front()值)是否相等。对于一个对称二叉树而言,对其进行广度遍历,从左往右读和从右往左读,是一样的。- 方法2:递归
双向并行判断,需要同时传入两个参数,只要一边为假,那么结果为假,采用&与运算符。
(注意传递参数和需要判定的参数)
方法1:递归
bool duichen(treeNode* leftchild, treeNode* rightchild)
{
//如果能够遍历到空节点,说明之前的状态都是true,对称的
if (leftchild == NULL && rightchild == NULL)
{
return true;
}
if (leftchild && rightchild && leftchild->data == rightchild->data)
{
//采用了&运算符 true&true = true,除此之外,皆为false
return (duichen(leftchild->Lchild, rightchild->Rchild) & duichen(leftchild->Rchild, rightchild->Lchild));
}
return false; //排除以上的其它情况
}
bool isSymmetrical(treeNode* rootNode)
{
return duichen(rootNode->Lchild, rootNode->Rchild);
}
方法2:双队列+广度遍历,同时进行比较。代码过程大部分同广度遍历,单纯的广度遍历是只存入一个队列即可,而这题需要存两个队列,并加入了一个判断条件。代码没有进行优化,冗余较多。
双队列同时成对入,成对出
- 若均为空,继续
- 其中一个为空,即有一边缺孩子,返回false;
- 都不为空,比较当前值
bool isSymmetrical(treeNode* rootNode)
{
if (rootNode == NULL)
{
return true;
}
queue<treeNode*>nodeQueue1;
queue<treeNode*>nodeQueue2;
nodeQueue1.push(rootNode);
nodeQueue2.push(rootNode);
while (!nodeQueue1.empty() && !nodeQueue2.empty())
{
treeNode* curNode1 = nodeQueue1.front();
treeNode* curNode2 = nodeQueue2.front();
//判断条件,孩子值不等,则不对称
if (curNode1->data != curNode2->data)
{
return false;
}
nodeQueue1.pop();
nodeQueue2.pop();
//先压栈,但必须得同时有孩子
if (curNode1->Lchild && curNode2->Rchild)
{
nodeQueue1.push(curNode1->Lchild);
nodeQueue2.push(curNode2->Rchild);
}
//左缺孩子或右缺孩子,都不对称
else if ((curNode1->Lchild && curNode2->Rchild == NULL) || (curNode1->Lchild == NULL && curNode2->Rchild))
{
return false;
}
//压栈
if (curNode1->Rchild && curNode2->Lchild)
{
nodeQueue1.push(curNode1->Rchild);
nodeQueue2.push(curNode2->Lchild);
}
//缺孩子
else if ((curNode1->Rchild && curNode2->Lchild == NULL) || (curNode1->Rchild == NULL && curNode2->Lchild))
{
return false;
}
}
return true;
}
方法3:队列/堆栈
只需要一个队列/堆栈,但使用队列和使用堆栈,稍微有点区别,存取的顺序不同,因为是成对进成对出,而queue是先进先出,stack是先进后出,所以在使用queue和stack时需要注意按什么样的顺序push。
stack:
treeNode* rightNode = nodeStack.top(); //这儿
nodeStack.pop();
treeNode* leftNode = nodeStack.top();
nodeStack.pop();
queue:
treeNode* leftNode = nodeQueue.top(); //这儿
nodeQueue.pop();
treeNode* rightNode = nodeQueue.top();
nodeQueue.pop();
bool isSymmetrical(treeNode* rootNode)
{
if (rootNode == NULL)
return true;
stack<treeNode*>nodeStack;
nodeStack.push(rootNode->Lchild);
nodeStack.push(rootNode->Rchild);
while (!nodeStack.empty())
{
treeNode* rightNode = nodeStack.top(); //这儿不同!!!!!
nodeStack.pop();
treeNode* leftNode = nodeStack.top(); //这儿不同!!!!!
nodeStack.pop();
if (leftNode == NULL && rightNode == NULL)
{
continue; //注意这儿!!!容易写成return true
}
if (leftNode == NULL || rightNode == NULL)
{
return false;
}
if (leftNode->data == rightNode->data )
{
nodeStack.push(leftNode->Lchild);
nodeStack.push(rightNode->Rchild);
nodeStack.push(leftNode->Rchild);
nodeStack.push(rightNode->Lchild);
}
else
{
return false;
}
}
}
7)完全二叉树
顺手写了下,不知道写的对不对,没有去查标准的代码,虽然自己的用例能通过,但不确定是否所有的用例都能通过。如果有错误,还望指出,thanks~
代码中if 判断部分图解
bool Complete(treeNode* left, treeNode* right)
{
if (right && (left == NULL))
return false;
if (right == NULL)
{
if (left && (left->Lchild || left->Rchild))
return false;
else
return true;
}
return Complete(left->Lchild, right->Rchild) && Complete(left->Lchild, left->Rchild)
&& Complete(left->Rchild, right->Lchild) && Complete(right->Lchild, right->Rchild);
}
bool isComplete(treeNode* rootNode)
{
if (rootNode == NULL)
return false;
return Complete(rootNode->Lchild, rootNode->Rchild);
}
8)满二叉树
只是遵循每层节点个数为 2^(i-1),i>=1。
用到的函数有 第三个算法 “3)二叉树节点个数”。
递归每一棵左右子树,判断左右子树节点个数是否相等,部分代码图解如下:
(纠正:下图中出现的两个isFullTree2修改成isFullTree)
int nodeNum(treeNode* rootNode)
{
if (rootNode == NULL)
{
return 0;
}
return (nodeNum(rootNode->Lchild) + nodeNum(rootNode->Rchild) + 1);
}
bool isFullTree(treeNode* rootNode)
{
if (rootNode)
{
if (nodeNum(rootNode->Lchild) != nodeNum(rootNode->Rchild))
return false;
}
else
return true;
isFullTree(rootNode->Lchild);
isFullTree(rootNode->Rchild);
}
9)平衡二叉树
判断一棵树是不是平衡二叉树,递归地查看每个节点的左右子树是否为平衡二叉树,判定条件是左右子树的高度差<=1。其中需要用到二叉树深度的计算。默认空树为平衡二叉树。
- 方法1:内部调用了深度计算的函数。从上至下递归,判断每个节点的左右子树是否为平衡二叉树。缺点:计算深度时,节点重复遍历。
TreeDepth函数往上翻。
bool isBalanceTree(treeNode* rootNode)
{
if (rootNode == NULL)
return true;
int leftdepth = TreeDepth(rootNode->Lchild);
int rightdepth = TreeDepth(rootNode->Rchild);
if ((leftdepth - rightdepth) * (leftdepth - rightdepth) > 1) //也可以用abs(绝对值函数)
return false;
return isBalanceTree(rootNode->Lchild) && isBalanceTree(rootNode->Rchild);
}
- 方法2:参照大佬的。从下至上。从下开始遍历节点深度,只要不满足条件,即可退出,不用继续往上遍历上面节点。将求深度和判断高度差相结合,运用三目运算符。其中-1用来代表该树出现不平衡,不管左树还是右树出现-1,都返回false。
//平衡二叉树优化(每个节点只遍历了一次)
int getDepth(treeNode* rootNode) {
if (rootNode == NULL) return 0;
int left = getDepth(rootNode->Lchild);
int right = getDepth(rootNode->Rchild);
if (left == -1 || right == -1)
return -1;
return abs(left - right) > 1 ? -1 : 1 + max(left, right);
}
bool IsBalanced_Solution(treeNode* rootNode)
{
return getDepth(rootNode) != -1;
}
为了便于理解,上面的 getDepth(treeNode *rootNode) 函数修改下
我对“-1”的理解:为什么需要-1?-1是怎么产生的?如果没有-1,能不能满足题目要求?
- 没有-1貌似也可以啊。其实没有-1,只有一个判断条件if (abs(left-right) > 1),return false; 也ok,但是平衡二叉树也包括空树,所以只有这个判断条件以及另一个判断条件 if(rootNode == NULL) return 0; 会把空树判定成非平衡二叉树,所以问题貌似出在了 if(rootNode == NULL) return 0; 这个判断语句的返回值0上。但涉及到求深度,这个条件只能return 0,这个返回值0改不了,所以想到另外创建一个返回值,只要在递归中出现这个返回值,那么判定return false。
- -1第一次是怎么产生的?一旦abs(left-right) > 1,-1就产生了,而且这个产生的过程是自下而上的,从最下边的小树判定开始的,小数中出现-1,上层的大树就不用考虑了。
- 综上所述,-1是为了避免空树的误判而产生的,因为一般情况下,返回0代表假,而在这道题中,空树也会返回0,所有不能用0来作为“假”的返回值,然后用了-1。
int getDepth(treeNode* rootNode)
{
if(rootNode == NULL)
return 0;
int left = getDepth(rootNode->Lchild);
int right = getDepth(rootNode->Rchild);
if(left == -1 || right == -1 || abs(left-right) > 1)
return false;
return max(left,right)+1; //求树的深度
}
10)搜索二叉树
每一个节点的左树节点均小于该节点,右树节点均大于该节点。
- 方法1:双堆栈+中序+非递归(目的,比较前后紧挨着的两个值是否递增)。
利用搜索二叉树的性质,搜索二叉树的中序排列是递增的。只要不满足这个条件,就不是搜索二叉树。所有创建了两个堆栈,一个用来对二叉树进行中序排列,另一个存放排好序的元素,然后对排好序的堆栈里的每一个元素进行判断:是否符合递增即可。
bool isBST(treeNode* rootNode)
{
stack<treeNode*>nodeStack;
stack<treeNode*>nodeStack2;
treeNode* cur = rootNode;
while (cur || !nodeStack.empty())
{
while (cur)
{
nodeStack.push(cur);
cur = cur->Lchild;
}
cur = nodeStack.top();
nodeStack.pop();
nodeStack2.push(cur); //对二叉树进行非递归中序排列,顺便加入到堆栈2
cur = cur->Rchild;
}
while (!nodeStack2.empty()) //对元素进行判断
{
cur = nodeStack2.top();
nodeStack2.pop();
if (!nodeStack2.empty())
{
if (cur->data <= nodeStack2.top()->data)
return false;
}
}
return true;
}
- 方法2:单堆栈+中序+非递归
这个是对上面方法1的优化,目的是一样的,只不过方法1开辟另一个堆栈存放每一个数据,其实可以开辟一个指针pre指向上一个值,一开始没想到用指针。反正都是比较上一个值和下一个值,所以只需要一个可以移动的保存上一个值的指针即可,不需要一个堆栈。
bool isBST(treeNode* rootNode)
{
stack<treeNode*>nodeStack;
treeNode* cur = rootNode;
treeNode* pre = NULL; //指向空容易易漏,否则提示未初始化
while (cur || !nodeStack.empty())
{
while (cur)
{
nodeStack.push(cur);
cur = cur->Lchild;
}
cur = nodeStack.top();
nodeStack.pop();
cout << cur->data << endl;
if (pre && pre->data >= cur->data)
return false;
pre = cur; //指针指向下一处
cur = cur->Rchild;
}
return true;
}
- 方法3:中序遍历+递归
要理解这个,先理解最简易的递归中序排列的走向。
中序递归能保证curNode(curNode是指当前根节点)的走向是从小到大的,我们只需要确定curNode的走向,然后另建一个指针/节点跟在curNode后面即可。比如下图,cur的走向一定是1,2,3,建一个pre指针,保存curNode的上一个节点,一边向前遍历,一边比较pre和curNode大小即可判断出是不是搜索二叉树。
(下面这图很简单,但对我理解代码来说,还是很有用的,留个记录,大佬忽略)
treeNode* pre = NULL;
bool isBST3(treeNode* rootNode)
{
if (rootNode == NULL)
return true ;
if (!isBST3(rootNode->Lchild))
{
return false;
}
if (pre && pre->data >= rootNode->data)
{
return false;
}
pre = rootNode;
if (!isBST3(rootNode->Rchild))
{
return false;
}
}
11)搜索二叉树的第K个节点
给定一棵二叉搜索树,请找出其中的第k小的结点。例如,(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
思路:搜索二叉树的中序排列本身就是递增的,可以从中序的递归和非递归入手。
- 方法1:非递归+中序(用了堆栈)
treeNode* KthNode(treeNode* rootNode, int k)
{
if (rootNode == NULL || k < 1)
return NULL;
stack<treeNode*>nodeStack;
treeNode* cur = rootNode;
int num = 0;
while (cur || !nodeStack.empty())
{
while (cur)
{
nodeStack.push(cur);
cur = cur->Lchild;
}
cur = nodeStack.top();
nodeStack.pop();
num += 1;
if (num == k)
{
return cur;
}
cur = cur->Rchild;
}
return NULL;
}
- 方法2:递归+中序(用了一个临时变量存储K值)
// 两个全局变量
int num = 0;
treeNode* temp = NULL;
treeNode* KthNode2(treeNode* rootNode, int k)
{
if (rootNode == NULL || k < 1)
return rootNode;
if (KthNode2(rootNode->Lchild, k) == temp && temp) // &&前后的两个条件不能调换顺序。
return temp;
num += 1;
if (num == k)
{
temp = rootNode;
return temp;
}
if (KthNode2(rootNode->Rchild, k) == temp && temp)
return temp;
}
- 方法3:递归+中序(参照大佬的)
int index = 0;
treeNode* KthNode3(treeNode* rootNode, int k)
{
if (rootNode != NULL)
{
treeNode* node = KthNode3(rootNode->Lchild, k);
if (node != NULL)
{
return node;
}
index++;
if (index == k)
{
return rootNode;
}
node = KthNode3(rootNode->Rchild, k);
if (node != NULL)
{
return node;
}
}
return NULL;
}
12)树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
整体代码思路:
for(pRoot1节点1;pRoot1不为空;pRoot1->left/right)
{
for(pRoot2节点1;pRoot2不为空;pRoot1->left/right)
{
if(pRoot1节点 = pRoot2节点)
continue;
else
return false;
}
}
bool isHasSubtree(treeNode* pRoot1, treeNode* pRoot2)
{
if (pRoot2 == NULL) // 必须先判断pRoot2再判断pRoot1
return true;
if (pRoot1 == NULL)
return false;
if (pRoot1->data == pRoot2->data)
{
return isHasSubtree(pRoot1->Lchild, pRoot2->Lchild)
&& isHasSubtree(pRoot1->Rchild, pRoot2->Rchild);
}
else
return false;
}
bool HasSubtree(treeNode* pRoot1, treeNode* pRoot2)
{
if (pRoot1 == NULL || pRoot2 == NULL)
return false;
//只要判定出 isHasSubtree(pRoot1, pRoot2)为真,之后的两个均可以不用判断,直接返回真
return isHasSubtree(pRoot1, pRoot2)
|| HasSubtree(pRoot1->Lchild, pRoot2) || HasSubtree(pRoot1->Rchild, pRoot2);
}
13)二叉搜索树的范围和
给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和。
二叉搜索树保证具有唯一的值。
示例 1:
输入:root = [10,5,15,3,7,null,18], L = 7, R = 15
输出:32
示例 2:输入:root = [10,5,15,3,7,13,18,1,null,6], L = 6, R = 10 输出:23
最后一句return sum,包含两层意思
第一层:if(rootNode == NULL) return 0;而一开始恰好将sum初始化为0。
if(rootNode == NULL) return 0;{xxxx}
替换成
if(rootNode)
{ xxxx }
return sum;
第二层:为了避免出现In member function rangeSumBST error: control reaches end of non-void function [-Werror=return-type] 这个错误(实际上是一个警告,到leetcode上编译的时候变成了一个错误),如果有出现过这个错误,见我的另一篇博客。
思路:
- 1.运用了flag标识,如果出现rootNode == L/R,一定会出现两次,第一次出现rootNode == L,flag=-1;第二次出现rootNode == R,flag=1。也就是说在flag=-1和flag=1的这个期间出现的curNode都需要加入到sum中,在flag未出现-1前以及flag出现1之后的curNode都需要排除掉。
- 2.判断条件if (flag == -1) 必须放在if (L == rootNode->data) 前判断,至于为什么,根据实际代码调整的结果+画图
缺陷
- 没有充分利用搜索二叉树中序排列的递增特性,貌似我这个算法适用于任何二叉树(节点不重复的),内存占用大,用了多个变量。
int sum = 0;
int flag = 0;
int rangeSumBST(treeNode* rootNode, int L, int R) {
if (rootNode)
{
rangeSumBST(rootNode->Lchild, L, R);
if (R == rootNode->data)
{
sum += R;
flag = 1;
}
if (flag == -1)
{
sum += rootNode->data;
}
if (L == rootNode->data)
{
sum += L;
flag = -1;
}
if (flag == 1)
{
return sum;
}
rangeSumBST(rootNode->Rchild, L, R);
}
return sum; //看这儿!!!!!
}
代码改进(利用递增特性)
只要判断当前节点与边界值的关系即可,不在该边界值以内的,均不需要加入,只有在这个边界值内的节点才加到“和”中。
int rangeSumBST2(treeNode* rootNode, int L, int R)
{
if (rootNode == NULL)
return 0;
if (rootNode->data <= R && rootNode->data >= L)
return rangeSumBST2(rootNode->Lchild, L, R) + rangeSumBST2(rootNode->Rchild, L, R) + rootNode->data;
else
return rangeSumBST2(rootNode->Lchild, L, R) + rangeSumBST2(rootNode->Rchild, L, R);
}
14)合并二叉树
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
treeNode* mergeTrees(treeNode* pRoot1, treeNode* pRoot2) {
if (pRoot1 == NULL)
return pRoot2;
if (pRoot2 == NULL)
return pRoot1;
pRoot1->data += pRoot2->data;
pRoot1->Lchild = mergeTrees(pRoot1->Lchild, pRoot2->Lchild);
pRoot1->Rchild = mergeTrees(pRoot1->Rchild, pRoot2->Rchild);
return pRoot1;
}
15)把二叉搜索树转换为累加树
给定一个二叉搜素树,将它转换成累加树,使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
思路:
- 还是利用二叉搜索树的中序递归,只不过在这题中,我采用了先遍历右树再遍历左树,也就是说会从树中最大的那个值开始进行“加和”。代码很简单。
int sum = 0;
treeNode* convertBST(treeNode* rootNode) {
if (rootNode)
{
convertBST(rootNode->Rchild); //先遍历右树
sum2 += rootNode->data; //用sum来囤积,以便修改下一个节点的值
rootNode->data = sum;
convertBST(rootNode->Lchild);
}
return rootNode;
}
16)二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。
我写的(进行了两组递归,效率低)
int length = 0;
int shendu(treeNode* root) //求深度
{
if (root == NULL)
return 0;
return max(shendu(root->Lchild), shendu(root->Rchild)) + 1;
}
int diameterOfBinaryTree(treeNode* rootNode)
{
if (rootNode == NULL)
return 0;
if ((shendu(rootNode->Lchild) + shendu(rootNode->Rchild)) > length)
length = shendu(rootNode->Lchild) + shendu(rootNode->Rchild); //用临时遍历记录上一条路径的长度
diameterOfBinaryTree(rootNode->Lchild); //对每个节点都求深度,将当前节点看作是根节点
diameterOfBinaryTree(rootNode->Rchild);
return length;
}
改进(时间效率更高)
int maxdepth = 0;
int shendu(treeNode* root)
{
if (root == NULL)
return 0;
int left = shendu(root->Lchild);
int right = shendu(root->Rchild);
maxdepth = max(maxdepth, left + right);
return max(left,right) + 1;
}
int diameterOfBinaryTree(treeNode* rootNode)
{
shendu(rootNode);
return maxdepth;
}