二叉树的基础及常用算法(代码+注释+图)(C++)

前面一些是基本概念和算法题,后面有一些是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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值