剑指offer-4:重建二叉树
目录
1问题描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
2问题分析
树存在先序遍历、中序遍历、后序遍历三种遍历方式。
- 先序遍历和中序遍历重建数组;
- 后序遍历和中序遍历重建数组。
思路标签:
- 算法:遍历、递归
- 数据结构:二叉搜索树
先序遍历序列的第一个数字是根结点值;
中序遍历中根结点的左边序列是左子树,右边序列是右子树。
解题还需要处理不符合条件的情况:
- 序列为空的情况;
- 两序列元素个数不同的情况;
- 两序列中元素不相同的情况。
3二叉树知识
3.1二叉树的概念
树是一些节点的集合,节点之间用边链接,节点之间不能有环路。上层的节点称为父节点,下层节点称为子节点。最上层的节点称为根节点。
二叉树是特殊的树。对于每个节点而言,与之直接相连的子节点不能超过两个(可以为0)。左边的子节点称为左子树,右边的子节点称为右子树。如下图就是一颗二叉树:
与树相关的一些概念:
没有任何子节点的节点称为树叶,或叶子节点。
深度:对于任意节点N,其深度指的是从根节点到N的唯一路径的长。根的深度为0。深度最深的叶子节点的深度为树的深度。可以理解为:树根是一个入口,离树根越远,就越深。如上图:A、B、C的深度为1,D、E的深度为2。
高: 对于任意节点N,从N到一片树叶的最远路径的长为N的高度。(只可以从从上到下不能经过从下到上的节点。)树叶的高为0。树的高为根的高。如上图,根的高度为2。A的高度为1,其他节点高度为0。
3.2二叉树的应用和时间复杂度
二叉树是一种常见的数据结构,常常用于查找,也运用于unix等常见操作系统的文件系统中。c++STL(标准模板库)中的set和map也使用二叉树中的红黑树实现。
- 二叉树的查找思想基于:在二叉树中,对于任意节点N,左子树中的所有项的值不大于节点N中存储的值,右子树中的所有项的值不小于节点N中存储的值。如下图:
这样,在查找时,只需要不断比较需要查找的x与N的大小,若小于N中的值,只需要搜索左子树,若大于N中的值,只需要搜索右子树。这样每次就能缩小搜索的范围。经过证明,普通二叉树的平均时间复杂度是O(LogN)。
看到这里,我们发现其实二叉树的搜索思想和二分查找一致,每次不断的减少搜索范围。但是二者之间还是有区别的。
对于二分查找而言,每次的时间复杂度不会超过O(LogN)。但是对于二叉树,搜索时间的复杂度取决于树的形状。在最坏情况下可能达到O(N)。如下图,如果要找到10,则要查找5次。
那我们为什么还要使用二叉树而不直接使用二分查找来代替?
这是因为,二分查找一般基于数组,如果需要插入或删除数据,则会带来很大的开销。因为每次插入或者删除数据需要将改变节点之后的数据往后挪或者往前挪。但是对于二叉树而言,只需要改变一下指向下一个节点的指针就可以很方便的实现插入或者删除。而且一些特殊的二叉树如红黑树可以保证查找的最坏复杂度不超过O(LogN)。
所以,如果是对于静态数据,不需要改变的数据而言,采用数组存储,使用二分查找比较好。而对于动态数据,需要频繁插入或者删除数据的,采取二叉树存储是较好的。
3.3二叉树的插入
思路:插入数据x,从根节点开始,不断比较节点与x的大小。若x小于节点,下一次比较x与节点的左子树,反之,比较x与节点的右子树。直到遇到一个空的节点,插入数据。(我们不考虑插入重复数据) 。如下图:
过程:比较4与7,4<7,再比较4与7的左子树6,4<6,比较4与6的左子树3,4>3,比较4与3的右子树,为空,插入4。
代码:
template< typename T>
void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) {
if ( nullptr == t ){
t = new BinaryNode (theElement);
} else if ( theElement < t->element ) {
insert( theElement, t->leftNode );
} else if ( theElement > t->element ) {
insert ( theElement, t->rightNode );
} else {//重复的数据不添加到树中
}
};
3.4二叉树的查找
思路:与插入类似,不断比较插入值与节点的值。带代码如下:
template< typename T>
bool BinaryTree<T>::isFind(const T &theElement, BinaryNode * t ) const {
if ( nullptr == t ){
return false;
} else if ( theElement < t->element ) {
return isFind( theElement, t->leftNode );
} else if ( theElement > t->element ) {
return isFind ( theElement, t->rightNode );
} else { //匹配
return true;
}
};
3.5二叉树的遍历
二叉树的遍历有三种方式:
前序遍历(DLR):首先访问根结点。然后如果有子树,则对于左孩子也采用DLR的遍历规则。没有就忽略。然后如果有右子树,则对右子树也采用DRL的遍历规则。没有就忽略。
中序遍历(LDR):首先访问根节点的左子树(对左子树也采用LDR),没有则忽略。再访问根节点。最后则对右子树也采用LDR的遍历规则,没有就忽略。
后序遍历:首先访问根节点的左子树(对左子树也采用LRD),没有则忽略。再对右子树也采用LRD的遍历规则,没有就忽略。最后则对右子树也采用LRD的遍历规则,没有就忽略。
三种遍历方式其实是根据根节点的访问顺序命名的。根最先方位为前序,次之访问为中序遍历。最后访问为后序遍历。
先序遍历图1的二叉树,结点的访问顺序为: e→b→a→d→c→f→g
中序遍历图1的二叉树,结点的访问顺序为:a→b→c→d→e→f→g
后序遍历图1的二叉树,结点的访问顺序为: a→c→d→b→g→f→e
- 这里采用递归方式实现:
前序遍历:
template< typename T>
void BinaryTree<T>::preOrder( BinaryNode *bNode ) const {
if( nullptr != bNode ) {
std::cout << bNode->element << " " ;
preOrder(bNode->leftNode);
preOrder(bNode->rightNode);
}
};
中序遍历:
template< typename T>
void BinaryTree<T>::inOrder( BinaryNode *bNode ) const {
if( nullptr != bNode ) {
inOrder(bNode->leftNode);
std::cout << bNode->element << " " ;
inOrder(bNode->rightNode);
}
};
后序遍历:
template< typename T>
void BinaryTree<T>::postOrder( BinaryNode *bNode ) const {
postOrder(bNode->leftNode);
postOrder(bNode->rightNode);
std::cout << bNode->element << " " ;
};
3.6二叉树的删除
二叉树的删除需要分三种情况考虑。
第一种,删除节点是树叶,则直接删除;
第二种是被删除的节点只有一个子节点,此时只需要将删除节点的上一个节点的指向该节点的指针指向该节点唯一的子节点;
第三种是被删除的节点有两个子节点,这种情况是最麻烦的。
我们采用的思想是将该节点的该节点右子树中最小的一个节点的值覆盖该节点中的值,然后再删除该节点的右子树中的最小的那个子节点。因为,该节点的右子树中的最小的那个子节点的值刚好大于被删除节点的左子树中所有的值,又小于被删除节点的右子树中所有的值。最小的那个子节点不可能有左子树,不然它就不是最小的节点,删除该节点就转换为删除一个只有一个子节点的节点,即第二种情况。
(1)第二种情况(删除节点7)
(2) 第三种情况(删除节点5)
其实是将5的那个节点赋值为6,然后删除节点6.
代码:
template< typename T>
void BinaryTree<T>::remove(const T &theElement, BinaryNode * &t ) {
if( nullptr == t ) {
return;
} else {
if ( theElement < t->element) {
remove(t->leftNode);
} else if ( theElement > t->element ) {
remove (t->rightNode);
} else if (nullptr != t->leftNode && nullptr != t->rightNode ) { //需要删除的节点两个儿子
t->element = findMin(t->rightNode)->element;
remove(t->element, t->rightNode);
} else {
BinaryNode * oldNode = t;
t = ( nullptr!= t->leftNode) ? t->leftNode : t->rightNode;
delete oldNode;
}
}
};
template< typename T>
typename BinaryTree<T>::BinaryNode * BinaryTree<T>::findMin(BinaryNode *bNode) const {
if ( nullptr!= bNode) {
while( nullptr != bNode->leftNode) {
bNode = bNode->leftNode;
}
}
return bNode;
}
参考博客:http://blog.youkuaiyun.com/u014182411/article/details/69831492
整个二叉树的工程文件在github上可以查阅:
https://github.com/yuanzoudetuzi/binaryTree
4二叉树基本操作
二叉树实现
1.创建二叉树
2.递归输出二叉树
2.1递归先序输出
2.2递归中序输出
2.3递归后序输出
3.非递归输出
3.1非递归先序输出
3.2非递归中序输出
3.3非递归后序输出
4.层次遍历二叉树
5.求树高
6.求树叶子节点
7.按值查找对应节点,输出左孩子结点值和右孩子结点值
8.计算所有节点数
参考博客:http://blog.youkuaiyun.com/j_anson/article/details/49888005
编译有错误!
/*
二叉树实现
1.创建二叉树
2.递归输出二叉树
2.1递归先序输出
2.2递归中序输出
2.3递归后序输出
3.非递归输出
3.1非递归先序输出
3.2非递归中序输出
3.3非递归后序输出
4.层次遍历二叉树
5.求树高
6.求树叶子节点
7.按值查找对应节点,输出左孩子结点值和右孩子结点值
8.计算所有节点数
*/
#include<iostream>
#include<string>
#include<stack>
#include<deque>
#include<fstream>
using namespace std;
//const int MAX_N = 100;
//数据节点
class Node
{
public:
char data;//数据
class Node *lchild;//左节点
class Node *rchild;//右节点
};
//二叉树
class Tree
{
public:
Tree(){}
~Tree(){}
//构建二叉树
void Create(string name)
{
ifstream readfile;
string str;
readfile.open(name);
if (readfile.is_open())
{
getline(readfile, str);//读取一行
}
readfile.close();
CreateNode(str);//构建二叉树
}
//先序遍历非递归算法
void Disp()
{
if (t == NULL)
{
return;
}
stack<Node *> m_stack;//定义栈
m_stack.push(t);
while (!m_stack.empty())
{
Node *p = m_stack.top();//赋值一份当前双亲节点
cout << p->data << ends;
m_stack.pop();
if (p->rchild)//先存储右子树,确保先输出左子树
{
m_stack.push(p->rchild);
}
if (p->lchild)//后存储左子树
{
m_stack.push(p->lchild);
}
}
}
//非递归中序遍历二叉树
void DispMid()
{
if (t == NULL)
{
return;
}
Node *p = t;
stack<Node *>m_stack;
while (p != NULL || !m_stack.empty())
{
while (p != NULL)//一路直走至左下角
{
m_stack.push(p);
p = p->lchild;
}
if (!m_stack.empty())
{
p = m_stack.top();//备份当前栈顶地址
m_stack.pop();
cout << p->data << ends;
p = p->rchild;
}
}
}
//非递归后序遍历二叉树
void DispBehid()
{
if (t == NULL)
{
return;
}
Node *pre = NULL, *p = t;
stack<Node *>m_stack;
while (p != NULL || !m_stack.empty())
{
while (p != NULL)//一路直走至左下角
{
m_stack.push(p);
p = p->lchild;
}
p = m_stack.top();
//右子树为空或者已访问,输出当前节点
if (p->rchild == NULL || p->rchild == pre)
{
cout << p->data << ends;
pre = p;//将当前结点地址赋值pre作为下一次判断标志,防止重复访问
m_stack.pop();
p = NULL;//p赋值空以便访问右子树
}
else
{
p = p->rchild;//访问子树的右子树
}
}
}
//层次遍历
void level_display()
{
if (t == NULL)
{
return;
}
deque<Node *>m_qu;//定义队列
m_qu.push_back(t);//树根入队列
while (!m_qu.empty())
{
Node *p = m_qu.front();//拷贝当前对头
cout <<p->data << ends;//输出
m_qu.pop_front();
if (p->lchild)//左孩子入队列
{
m_qu.push_back(p->lchild);
}
if (p->rchild)//右孩子入队列
{
m_qu.push_back(p->rchild);
}
}
}
//递归先序遍历输出二叉树
void display()
{
cout << "递归先序:";
output(t);
cout << endl;
}
//递归中序遍历输出二叉树
void displayMid()
{
cout << "递归中序:";
outputMid(t);
cout << endl;
}
//递归后序遍历输出二叉树
void displayBhind()
{
cout << "递归后序";
outputBhind(t);
cout << endl;
}
//二叉树高度
void Height()
{
int height = get_height(t);
cout << "Height: " << height << endl;
}
//输出叶子节点值
void display_leaf()
{
cout << "Leaves: ";
output_leaf(t);
cout << endl;
}
//查找二叉树中值data域为elem的节点
void find_node(char elem)
{
Node *res = NULL;
res = find_node(t, elem, res);
if (res != NULL)
{
cout << "nice." << endl;
if (res->lchild)
{
cout << "left child:";
cout << leftchild(res)->data << endl;
}
if (res->rchild)
{
cout << "right child:";
cout << rightchild(res)->data << endl;
}
}
else
{
cout << "NO." << endl;
}
}
//计算节点数
void nodes_count()
{
int sum;
if (t == NULL)//若为空,则0个节点
{
sum = 0;
}
else
{
sum = node_count(t);
cout << "Total Nodes:" << sum + 1 << endl;
}
}
private:
Node *t;
//构建二叉树
void CreateNode(string str)
{
stack<Node *> m_stack;
Node *p;
int k;
while (str.length() != 0)
{
//若当前为'(',将双亲节点推入栈,下一位存储的p值作为左节点处理
if (str[0] == '(')
{
m_stack.push(p); k = 1;
}
//为右括号则栈顶退出一位
else if (str[0] == ')')
{
m_stack.pop();
}
//为',',则下一个字符作右节点处理
else if (str[0] == ',')
{
k = 2;
}
//存储值用作双亲结点
else
{
p = (Node *)malloc(sizeof(Node));
p->data = str[0];
p->lchild = p->rchild = NULL;
//树根为空时,将第一个节点作为树根并赋值给私有成员变量
if (t == NULL)
{
t = p;
}
//树根不为空
else
{
if (k == 1)//作为左节点处理,将栈中双亲节点的左指针指向当前节点
{
m_stack.top()->lchild = p;
}
else//作为右节点处理
{
m_stack.top()->rchild = p;
}
}
}
//重构串,除去首字符,并将串长度减小1
str.assign(str.substr(1, str.length() - 1));
}
}
//递归先序遍历输出二叉树
void output(Node *t)
{
if (t != NULL)//当树根不为空时
{
cout << t->data;//输出
if (t->lchild != NULL || t->rchild != NULL)//左/右结点不为空时递归到下一层
{
cout << "(";
output(t->lchild);
if (t->rchild != NULL)//当左节点遍历结束后,左节点递归返回一层,递归右节点
{
cout << ",";
}
output(t->rchild);
cout << ")";
}
}
}
//递归中序遍历二叉树
void outputMid(Node *t)
{
if (t == NULL)//空则返回
{
return;
}
else
{
cout << "(";
outputMid(t->lchild);//递归左孩子节点
if (t->rchild != NULL)
{
cout << ",";
}
cout << t->data;//输出
outputMid(t->rchild);//递归右孩子结点
cout << ")";
}
}
//递归后序遍历输出二叉树
void outputBhind(Node *t)
{
if (!t)//空则返回
{
return;
}
else
{
cout << "(";
outputBhind(t->lchild);//递归左孩子节点
if (t->rchild != NULL)
{
cout << ",";
}
outputBhind(t->rchild);//递归右孩子结点
cout << t->data;//输出
cout << ")";
}
}
//求树高
int get_height(Node *t)
{
int leftheight, rightheight;
if (t == NULL)//递归至不存在子节点时返回0
{
return 0;
}
else
{
leftheight = get_height(t->lchild);//递归求左子树高度
rightheight = get_height(t->rchild);//递归其右子树高度
return leftheight > rightheight ? leftheight+1 : rightheight+1;//递归返回时返回最大值
}
}
//查找左节点
Node *leftchild(Node *p)
{
return p->lchild;
}
//查找右节点
Node *rightchild(Node *p)
{
return p->rchild;
}
//输出叶子节点
void output_leaf(Node *t)
{
if (t != NULL)//树根不为空时
{
//当前节点没有子节点时输出节点数据
if (t->lchild == NULL&&t->rchild == NULL)
{
cout << t->data << ends;
}
output_leaf(t->lchild);//递归左子树
output_leaf(t->rchild);//递归右子树
}
}
//查找二叉树中值data域为elem的节点
Node * find_node(Node *t, char elem, Node *res = NULL)
{
//Node *res = NULL;
if (t == NULL)//若当前节点为空,则返回结束
{
return NULL;
}
else
{
if (t->data == elem)//若找到值,返回地址
{
//res = t;
return t;
}
else
{
if (res == NULL)//若保存结果的指针不为空,则递归查找左节点
{
res = find_node(t->lchild, elem, res);
}
if (res == NULL)//若保存结果的指针不为空,且左节点为搜索到,则递归查找右节点
{
res = find_node(t->rchild, elem, res);
}
}
return res;
}
}
//计算节点数
int node_count(Node *t)
{
int lcount = 0, rcount = 0;
if (t == NULL)//空则返回
{
return 0;
}
else
{
if (t->lchild != NULL)//遍历左孩子节点
{
lcount = node_count(t->lchild);
lcount += 1;
}
if (t->rchild != NULL)//遍历右孩子节点
{
rcount = node_count(t->rchild);
rcount += 1;
}
return lcount + rcount;//返回当前左右孩子节点数
}
}
};
int main()
{
Tree m_tree;
m_tree.Create("data");
m_tree.display();//递归先序输出
m_tree.displayMid();//递归中序输出
m_tree.displayBhind();//递归后序输出
m_tree.Height();//树高
m_tree.display_leaf();//叶子节点
cout << "非递归先序:";
//cout << "Fir:";
m_tree.Disp();//非递归先序遍历
cout << endl;
cout << "非递归中序:";
//cout << "Mid:";
m_tree.DispMid();//非递归中序遍历
cout << endl;
cout << "非递归后序:";
//cout << "Bac:";
m_tree.DispBehid();//非递归后序遍历
cout << endl;
cout << "层次遍历:";
m_tree.level_display();//层次遍历
cout << endl;
cout << "Input element:";
char elem;
cin >> elem;
m_tree.find_node(elem);//按节点值查找
m_tree.nodes_count();//计算节点数
return 0;
}
测试结果
5题目答案
给前序遍历、中序遍历重构
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
if (pre.size() == 0 || vin.size() == 0)
return nullptr;
int *preStart = &pre[0];
int *preEnd = &pre[pre.size() - 1];
int *vinStart = &vin[0];
int *vinEnd = &vin[vin.size() - 1];
return ConstructCore(preStart, preEnd, vinStart, vinEnd);
}
TreeNode* ConstructCore(int* preStart, int* preEnd, int* vinStart, int* vinEnd)
{
int rootValue = preStart[0];
TreeNode* root = new TreeNode(preStart[0]);
root->val = rootValue;
root->left = nullptr;
root->right = nullptr;
if (preStart == preEnd)
{
if (vinStart == vinEnd && *preStart == *vinStart)
return root;
}
int *rootVin = vinStart;
while (rootVin < vinEnd && *rootVin != rootValue)
++rootVin;
int leftLength = rootVin - vinStart;
int* preLeftEnd = preStart + leftLength;
if (leftLength > 0)
{
//构建左子树
root->left = ConstructCore(preStart + 1, preLeftEnd, vinStart, rootVin - 1);
}
if (leftLength < preEnd - preStart)
{
//构建右子树
root->right = ConstructCore(preLeftEnd + 1, preEnd, rootVin + 1, vinEnd);
}
return root;
}
};
6扩展
给中序、后序排列重构
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> post, vector<int> in){
int postLength = post.size();
int inLength = in.size();
if (postLength == 0 || inLength == 0) return nullptr;
int* postStart = &post[0];
int* postEnd = &post[postLength - 1];
int* inStart = &in[0];
int* inEnd = &in[inLength - 1];
return ConstructCore(postStart, postEnd, inStart, inEnd);
}
TreeNode* ConstructCore(int* postStart, int* postEnd, int* inStart, int* inEnd)
{
int rootValue = *postEnd;
TreeNode* root = new TreeNode(rootValue);
if (postStart == postEnd) {
if (inStart == inEnd && *inStart == *postStart)
return root;
else
throw std::exception("Invalid value!");
}
int* inRoot = inStart;
while (inRoot < inEnd && *inRoot != rootValue)
++inRoot;
if (inRoot == inEnd && *inRoot != rootValue)
throw std::exception("Invalid value!");
int rightLength = inEnd - inRoot;
int* rightPostStart = postEnd - rightLength;
if (rightLength > 0) {
root->right = ConstructCore(rightPostStart, postEnd - 1, inRoot + 1, inEnd);
}
if (rightLength < postEnd - postStart) {
root->left = ConstructCore(postStart, rightPostStart - 1, inStart, inRoot - 1);
}
return root;
}
};