创建二叉树
(1)二叉树的反序列化
给定“6423####51##7##”(先序)这种序列,构造二叉树(假设只出现0~9数字,如果要扩展可以加空格)
TreeNode* preOrderToTree(string input,int& index)
{
if(input.length() == 0)return NULL;
if(input.at(index) == '#')
{
index++;
return NULL;
}
TreeNode* Node = new TreeNode();
Node->val = input.at(index) - '0';
index++;
Node->left = preOrderToTree(input, index);
Node->right = preOrderToTree(input, index);
return Node;
}
(2)前序+中序重建二叉树(剑指offer6)
首先preOrder[begin]为根节点root;
每次在中序序列中找preOrder[begin],然后可以将中序分成两个子序列,左边一部分递归构造root->left;右边一部分构造root->right;
TreeNode* TreeBuild(vector<int> preOrder, vector<int> middleOrder,
int preBegin, int preEnd, int middleBegin,int middleEnd)
{
if(preOrder.size() == 0 || middleOrder.size() == 0 || middleOrder.size() != preOrder.size())return NULL;
int rootValue = preOrder.at(preBegin);
TreeNode* root = new TreeNode();
root->val = rootValue;
root->left = root-> right = NULL;
if(preBegin == preEnd){
if(middleBegin == middleEnd && preOrder.at(preBegin) == middleOrder.at(middleBegin))
return root;
else
throw std::exception("invalid input");
//return NULL;
}
//在中序遍历中找rootValue
int index = middleBegin;
while(index <= middleEnd && middleOrder.at(index) != rootValue)++index;
if(index == middleEnd && middleOrder.at(index) != rootValue)
throw std::exception("invalid input");
//return NULL;
int leftLength = index - middleBegin;
int rightLength = middleEnd - index;
if(leftLength > 0)
root->left = TreeBuild(preOrder, middleOrder,preBegin+1, preBegin + leftLength, middleBegin, index-1);
if(rightLength > 0)
root->right = TreeBuild(preOrder, middleOrder,preBegin + leftLength + 1, preEnd, index + 1, middleEnd);
return root;
}
二叉树的遍历
要活用三种遍历的方式:
当问题中需要根左右、左根右、左右根的顺序对节点进行判断的时候,要能够立马想到这三种遍历方式。
(1)二叉树三种遍历的递归版本
void preOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
cout<<Tree->val;
preOrder(Tree->left);
preOrder(Tree->right);
}
void middleOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
middleOrder(Tree->left);
cout << Tree->val;
middleOrder(Tree->right);
}
void backOrder(TreeNode* Tree)
{
if(Tree == NULL) return ;
backOrder(Tree->left);
backOrder(Tree->right);
cout << Tree->val;
}
(2)二叉树的三种遍历非递归实现
非递归主要是利用栈的特性来实现。
//迭代的先序遍历的思想就是每次都向左下遍历,当访问到一个节点时,先
//将该节点的值打印,然后将该节点入栈,一直到为空
//下一步就是取出栈顶,该点是已经遍历过了的,所以只需要取其右子节点
//继续向左下遍历即可
void preOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
stack<TreeNode*> s;
TreeNode* pCur = Tree;
while(pCur != NULL || !s.empty()){
//首先遍历到最左下节点
if(pCur != NULL){
cout << pCur->val;
s.push(pCur);
pCur = pCur->left;
}else{ //当左下为空的时候,回到上一个元素,继续不停向左下遍历
pCur = s.top()->right;
s.pop();
}
}
}
//迭代的中序遍历与先序类似,不同的地方是,先入栈,出栈的时候再输出
void middleOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
stack<TreeNode*> s;
TreeNode* pCur = Tree;
while(pCur != NULL || !s.empty()){
if(pCur != NULL){
s.push(pCur);
pCur = pCur->left;
}else{
pCur = s.top();
cout << pCur->val;
s.pop();
pCur = pCur->right;
}
}
}
//迭代的后序遍历与之前的稍有不同
//采用的技巧是将孩子节点入栈后,对节点执行左右置空的操作
//只有遇到空节点才输出,此时保证了该节点的左右孩子节点已经被访问到
void backOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
stack<TreeNode*> s;
s.push(Tree);
TreeNode* pCur = NULL;
while(!s.empty()){
pCur = s.top();
if(pCur->left == NULL && pCur->right == NULL){
cout << pCur->val;
s.pop();
}else{
//先右后左入栈才能保障先左后右出栈
if(pCur->right != NULL){
s.push(pCur->right);
pCur->right = NULL;
}
if(pCur->left != NULL){
s.push(pCur->left);
pCur->left = NULL;
}
}
}
}
(3)二叉树的层次遍历
主要利用队列的先入先出特性
void levelOrder(TreeNode* Tree)
{
if(Tree == NULL)return;
queue<TreeNode*> q;
q.push(Tree);
TreeNode* pCur;
while(!q.empty()){
pCur = q.front();
q.pop();
cout << pCur->val;
if(pCur->left != NULL)
q.push(pCur->left);
if(pCur->right != NULL)
q.push(pCur->right);
}
}
变形:按行打印二叉树(剑指offer 60)
思路:维护两个变量,pLast表示当前层最右边的节点,pNextLast表示下一层最右边的节点
void levelOrderByline(TreeNode* Tree)
{
if(Tree == NULL)return;
queue<TreeNode*> q;
q.push(Tree);
TreeNode* pLast = Tree;
TreeNode* pNextLast = NULL;
while(!q.empty()){
TreeNode* pCur = q.front();
q.pop();
cout << pCur->val;
if(pCur->left != NULL)
{
q.push(pCur->left);
pNextLast = pCur->left;
}
if(pCur->right != NULL)
{
q.push(pCur->right);
pNextLast = pCur->right;
}
if(pCur == pLast){
cout << endl;
pLast = pNextLast;
pNextLast = NULL;
}
}
}
其他的一些算法题
(1)求二叉树叶子节点个数
左子树叶子节点个数+右子树叶子节点个数;用递归实现
(2)求二叉树高度(剑指offer39)
int getHeight(TreeNode* Tree)
{
if(Tree == NULL)return 0;
int left = getHeight(Tree->left);
int right = getHeight(Tree->right);
return 1 + max(left, right);
}
变形:判断一棵树是不是平衡二叉树
可以仿照上述做法,求出左右子树的高度然后进行判断,但是这个时候一个节点可能会遍历多次。
较好的做法:利用后序遍历最后遍历根节点的特性,记录一个深度信息
bool isBalance(TreeNode* Tree, int& depth)
{
if(Tree == NULL){
depth = 0;
return true;
}
int left,right; //用来记录高度信息
if(isBalance(Tree->left, left) && isBalance(Tree->right, right)){
if(abs(left - right) <= 1){
depth = 1 + max(left, right);
return true;
}
}
depth = 1 + max(left, right);
return false;
}
(3)二叉树的镜像(剑指offer 19)
解题思路:先序遍历并且交换左右子树,对左右子树执行镜像操作。
void mirror_tree(TreeNode* T)
{
if(T==NULL)
return;
if((T->left==NULL) && (T->right==NULL))
return;
TreeNode* temp = T->left;
T->left=T->right;
T->right=temp;
mirror_tree(T->left);
mirror_tree(T->right);
}
(4)树的子结构判断(剑指offer 18)
给pRoot1和pRoot2,判断pRoot2是不是pRoot1子结构。
第一步:遍历pRoot1中的每个节点,找pRoot2->val;(递归的先序遍历即可完成)
第二步:判断以该节点为根的子树是不是和pRoot2一样。
bool has_tree(TreeNode* T1,TreeNode* T2)
{
if(T2==NULL)
return true;
if(T1==NULL)
return false;
if(T1->value!=T2->value)
return false;
return has_tree(T1->left,T2->left)&&has_tree(T1->right,T2->right);
}
bool has_sub(TreeNode* T1,TreeNode* T2)
{
bool result=false;
if(T1!=NULL && T2!=NULL)
{
if(T1->value==T2->value)
result=has_tree(T1,T2);
if(!result)
result=has_sub(T1->left,T2);
if(!result)
result=has_sub(T1->right,T2);
}
return result;
}
(5)寻找二叉树中和为某一值的路径(剑指offer25)
解题思路:保存当前已有路径,当循环到根节点时,进行判断
void findPath(TreeNode* Tree, vector<TreeNode*>& nodes, int num, int target)
{
if(Tree == NULL)return;
num += Tree->val;
nodes.push_back(Tree);
if(Tree->left == NULL && Tree->right == NULL){
if(num == target){
for(auto i : nodes)cout<<i->val;
cout << endl;
}
}
else{
if(Tree->left != NULL){
findPath(Tree->left, nodes, num, target);
}
if(Tree->right != NULL){
findPath(Tree->right, nodes, num, target);
}
}
nodes.pop_back();
}
(6)序列判断
判断某一序列是不是二叉搜索树的后序遍历序列(剑指offer24)
思路:对某一个节点:①寻找前面全部小于根值的一部分为左子树序列,判断剩余一部分是不是都大于根值②判断左子树序列是不是二叉搜索树③判断右子树序列是不是二叉搜索树
bool back_tree(int* arr,int length)
{
if(arr==NULL||length<=0)
return false;
int root=arr[length-1];
int i=0;
while(arr[i]<root && i<length-1)
i++;
int j=i;
for(;j<length-1;j++)
{
if(arr[j]<root)
return false;
}
bool left=true;
if(i>0)
left=back_tree(arr,i);
bool right=true;
if(i<length-1)
right=back_tree(arr+i,length-i-1);
return (left&&right);
}
(7)二叉搜索树转换成双向链表(剑指offer27)
//找树的最左节点,即转换之后链表的头结点
TreeNode* find_most_left(TreeNode* T)
{
if(T==NULL)
return NULL;
TreeNode* pCur = T;
while(pCur->left!=NULL)
pCur = pCur->left;
return pCur;
}
//进行整棵树的转换
TreeNode* convert(TreeNode* T)
{
TreeNode* phead=find_most_left(T);
TreeNode* last_node=NULL;
convert_node(T,&last_node);
return phead;
}
//进行单个节点的转换
void convert_node(TreeNode* T,TreeNode** lastnode)
{
if(T==NULL)
return;
if(T->left!=NULL)
convert_node(T->left,lastnode);
T->left=*lastnode;
if(*lastnode!=NULL)
(*lastnode)->right=T;
*lastnode=T;
if(T->right!=NULL)
convert_node(T->right,lastnode);
}
类似的:二叉树转换成单链表(leetcode 114)
思路:采用一种类似于后序遍历的方式,先处理右子节点,再处理左子节点最后处理根节点,用一个指针记录之前处理的节点,代表当前节点应该衔接哪一个节点
public:
//采用一种伪后序遍历的方式
void flatten(TreeNode* root) {
if(root == NULL)return;
flatten(root->right);
flatten(root->left);
root->left = NULL;
root->right = pPre;
pPre = root;
}
private:
TreeNode* pPre = NULL;
(8)树的两节点最低公共祖先问题(剑指offer50)
①二叉搜索树
只需要判断节点的值,如果给的两节点val同时小于(或者大于)根节点val,那么公共节点一定在左子树(或者右子树)中,直到找到第一个节点值位于两个给定节点值之间
TreeNode* findParent(TreeNode* Tree, TreeNode* Node1, TreeNode* Node2)
{
if(Tree == NULL)return NULL;
if(Tree->val > Node1->val && Tree->val > Node2->val)
return findParent(Tree->left, Node1, Node2);
if(Tree->val < Node1->val && Tree->val < Node2->val)
return findParent(Tree->right, Node1, Node2);
return Tree;
}
②普通二叉树
也可以采用递归的形式判断是否在左右子树中,不过开销很大。
高效的做法:先求出根节点到两个节点的路径,进而转换成求两条路径的公共节点(类似于求两条单链表的第一个公共节点)
bool FindNodePath(TreeNode* T,vector<TreeNode*>& path,int nodeKey)
{
path.push_back(T);
if(T->value==nodeKey)
return true;
bool found = false;
if(T->left!=NULL)
found=FindNodePath(T->left,path,nodeKey);
if(!found && T->right!=NULL)
found=FindNodePath(T->right,path,nodeKey);
if(!found)
path.pop_back();
return found;
}
TreeNode* GetLastCommonNode(vector<TreeNode*>& path1,vector<TreeNode*>& path2)
{
vector<TreeNode*>::iterator it1=path1.begin();
vector<TreeNode*>::iterator it2=path2.begin();
TreeNode* result=NULL;
while(it1!=path1.end() && it2!=path2.end())
{
if(*it1==*it2)
result=*it1;
it1++;
it2++;
}
return result;
}
(9)二叉树中节点的最大距离
解题思路:最大距离为三种情况:①左子树中最大距离②右子树中最大距离③左子树到根最大+右子树到根最大
int FarestNode(TreeNode* Tree, int& leftMax, int& rightMax)
{
if(Tree == NULL){
leftMax = 0;
rightMax = 0;
return 0;
}
int maxLL,maxLR,maxRL,maxRR;
int farestLeft,farestRight; //左右子树的最大距离
if(Tree->left != NULL){
farestLeft = FarestNode(Tree->left, maxLL, maxLR);
leftMax = max(maxLL, maxLR) + 1;
}else{
farestLeft = 0;
leftMax = 0;
}
if(Tree->right != NULL){
farestRight = FarestNode(Tree->right, maxRL, maxRR);
rightMax = max(maxRL, maxRR) + 1;
}else{
farestRight = 0;
rightMax = 0;
}
return max(max(farestLeft, farestRight), leftMax + rightMax);
}
注:求深度的时候,通常将depth信息作为参数进行递归,这个时候参数必须是引用传参!!
(10)判断一棵树是否是完全二叉树
主要是利用广度优先搜索,如果遍历到了空节点那么,之后应该都为空节点,否则就不是完全二叉树
bool isCompleteBinaryTree(TreeNode* Tree)
{
if(Tree == NULL)return false;
queue<TreeNode*> q;
q.push(Tree);
TreeNode* pCur;
//与正常的层次遍历不同的是如果左右节点为空节点,也将其放入到队列中
while((pCur = q.front()) != NULL){
q.push(pCur->left);
q.push(pCur->right);
q.pop();
}
//此时已经遍历到空节点,按照完全二叉树的定义,后面的节点应该都是空节点
while(!q.empty()){
pCur = q.front();
q.pop();
if(pCur != NULL)return false;
}
return true;
}
(11)判断一颗二叉树是否是对称的
解题思路:定义一种新的遍历序列(先右子树后左子树,然后看它与二叉树的前序遍历结果是否一样)
bool func(TreeNode* left,TreeNode* right)
{
if(left==NULL&&right==NULL)
return true;
if(left==NULL||right==NULL)
return false;
if(left->value==right->value)
{
return func(left->left,right->right)&&func(left->right,right->left);
}
return false;
}
(12)二叉搜索树中的第k个节点
TreeNode* KthNodeCore(TreeNode* T,int& k)
{
TreeNode* result= NULL;
if(T->left!=NULL)
result=KthNodeCore(T->left,k);
if(result==NULL)
{
if(k==1)
result=T;
--k;
}
if(result==NULL&&T->right!=NULL)
result = KthNodeCore(T->right,k);
return result;
}
(13)给正整数n,用1,2,3….n构建所有可能的二叉搜索树(leetcode 95)
解题思路:利用递归进行求解
vector<TreeNode*> generateTrees(int n) {
if(n < 1)return vector<TreeNode*>();
return generateTreesCore(1, n);
}
vector<TreeNode*> generateTreesCore(int start, int end)
{
vector<TreeNode*> result;
if(start > end){
result.push_back(NULL);
return result;
}
if(start == end){
result.push_back(new TreeNode(start));
return result;
}
vector<TreeNode*> left,right;
for(int i = start; i <= end; ++i){
left = generateTreesCore(start, i-1);
right = generateTreesCore(i+1, end);
for(auto lNode: left){
for(auto rNode: right){
TreeNode* root = new TreeNode(i);
root->left = lNode;
root->right = rNode;
result.push_back(root);
}
}
}
return result;
}
(14)之字形打印二叉树(剑指offer 61)
解题思路:利用两个栈进行层次遍历,一个栈负责先左后右入栈,一个栈负责先右后左入栈
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>>result;
if(root == NULL)return result;
stack<TreeNode*> s[2];
int currentStack = 0;
s[currentStack].push(root);
vector<int> line;
while(!s[0].empty() || !s[1].empty()){
TreeNode* pCurrent = s[currentStack].top();
s[currentStack].pop();
line.push_back(pCurrent->val);
if(currentStack == 0){ //如果是第一个栈就从左往右入栈,这样就能保证从右往左出栈
if(pCurrent -> left != NULL)s[1 - currentStack].push(pCurrent->left);
if(pCurrent -> right != NULL)s[1 - currentStack].push(pCurrent->right);
}
if(currentStack == 1){ //如果是第二个栈就从右往左入栈,这样就能保证从左往右出栈
if(pCurrent -> right != NULL)s[1 - currentStack].push(pCurrent->right);
if(pCurrent -> left != NULL)s[1 - currentStack].push(pCurrent->left);
}
if(s[currentStack].empty()){
currentStack = 1 - currentStack;
result.push_back(line);
line.clear();
}
}
return result;
}