1、二叉树的构造
#include <iostream>
#include <cassert>
#include <queue>
#include <stack>
using namespace std;
template <class T>
struct TreeNode
{
TreeNode(const T& value = T())
:_value(value)
,_lchild(0)
,_rchild(0)
{}
T _value;//节点的值
TreeNode<T>* _lchild;//左孩子
TreeNode<T>* _rchild;//右孩子
};
template <class T>
class BinaryTree
{
public:
typedef TreeNode<T> Node;
BinaryTree()//无参构造函数
:_root(NULL)
{}
BinaryTree(const T* a,size_t size,const T& invalid)//构造函数
{
assert(a);
size_t index = 0;
_root = CreatTree(a,size,invalid,index);
}
BinaryTree(const BinaryTree<T>& b)//拷贝构造
{
_root = Copy(b._root);
}
//现代写法的赋值运算符重载1
BinaryTree& operator=(const BinaryTree<T>& b)
{
if (this != &b)
{
Node* tmp = Copy(b._root);
Destroy(_root) ;
_root = tmp;
//swap(_root,tmp);
}
return *this;
}
////现代写法的赋值运算符重载2
//BinaryTree& operator=(BinaryTree<T> b)
//{
// swap(b._root,_root);
// return *this;
//}
~BinaryTree()//析构
{
if (NULL != _root)
{
Destroy(_root);
_root = NULL;
}
}
protected:
//按照先序遍历递归建树
Node* CreatTree(const T* a,size_t size,const T& invalid,size_t& index)//注意index的传值方式
{
assert(a);
Node* root = NULL;
if (a[index] != invalid && index < size)//按先序遍历建树
{
root = new Node(a[index]);
root->_lchild = CreatTree(a,size,invalid,++index);
root->_rchild = CreatTree(a,size,invalid,++index);
}
return root;
}
//拷贝对象
Node* Copy(Node* root)
{
Node* tmp = NULL;
if(root)
{
tmp = new Node(root->_value);
tmp->_lchild = Copy(root->_lchild);
tmp->_rchild = Copy(root->_rchild);
}
return tmp;
}
//释放空间
void Destroy(Node*& root)
{
if(root)//用后序遍历方式释放空间
{
Destroy(root->_rchild);
Destroy(root->_lchild);
delete root;
root = NULL;
}
}
private:
Node* _root;//根节点
};
2、遍历二叉树
二叉树的遍历方式一共分为四种:先序遍历、中序遍历、后序遍历和层序遍历,而前三种遍历又分为递归遍历和非递归遍历,层序遍历则是借助队列先进先出的特性来辅助完成。
【递归遍历二叉树】
void preorder(Node* root)//先序遍历打印树的各个节点
{
if (root)
{
cout<<root->_value<<" "; //访问当前节点的值
preorder(root->_lchild); //递归遍历左子树
preorder(root->_rchild); //递归遍历右子树
}
}
void inorder(Node* root)//中序遍历打印树的各个节点
{
if (root)
{
preorder(root->_lchild); //递归遍历左子树
cout<<root->_value<<" "; //访问当前节点的值
preorder(root->_rchild); //递归遍历右子树
}
}
void postorder(Node* root)//后序遍历打印树的各个节点
{
if (root)
{
preorder(root->_lchild); //递归遍历左子树
preorder(root->_rchild); //递归遍历右子树
cout<<root->_value<<" "; //访问当前节点的值
}
}
void levelorder(Node* root)//层序遍历打印树的各个节点
{
queue<Node*> q;
if (root)
{
q.push(root);//将根节点插进队列
}
while(!q.empty())
{
Node* front = q.front();
q.pop();
cout<<front->_value<<" ";
if (front->_lchild)
{
q.push(front->_lchild);
}
if (front->_rchild)
{
q.push(front->_rchild);
}
}
}
【非递归遍历二叉树】
提供给外部的接口:
public:
void PreOrder()//先序遍历打印树的各个节点
{
cout<<"先序遍历:";
preorderR(_root);
cout<<endl;
}
void InOrder()//中序遍历打印树的各个节点
{
cout<<"中序遍历:";
inorderR(_root);
cout<<endl;
}
void PostOrder()//后序遍历打印树的各个节点
{
cout<<"后序遍历:";
postorderR(_root);
cout<<endl;
}
void LevelOrder()//层序遍历打印树的各个节点
{
cout<<"层序遍历:";
levelorder(_root);
cout<<endl;
}
算法实现:
void preorderR(Node* root)//先序遍历打印树的各个节点
{
Node* cur = root;
stack<Node*> s;
while (!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完
{
while(cur)//先序遍历,遇到树根节点直接访问数据并将其压栈
{
cout<<cur->_value<<" ";
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取出栈顶元素,到此说明此节点以及其左子树已经访问过了
s.pop();
cur = top->_rchild;//以子问题的方式去访问右子树
}
cout<<endl;
}
void inorderR(Node* root)//中序遍历打印树的各个节点
{
Node* cur = root;
stack<Node*> s;
while(!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完
{
while(cur)//中序遍历,遇到树根节点直接将其压栈
{
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取出栈顶元素,到此说明此节点的左子树已经访问过了
cout<<top->_value<<" ";//访问栈顶元素(即根节点)
s.pop();
cur = top->_rchild;//以子问题的方式去访问右子树
}
cout<<endl;
}
void postorderR(Node* root)//后序遍历打印树的各个节点
{
Node* cur = root;
Node* prev = NULL;//上一个访问过的数据
stack<Node*> s;
while(!s.empty() || cur)//只要当前节点和栈不同时为空,就说明树没遍历完
{
//后序遍历,遇到树根节点直接将其压栈
while(cur)
{
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取栈顶元素,但不一定能访问
//当节点右子树为空或已经访问过时对其直接进行访问
if (top->_rchild==NULL || top->_rchild==prev)
{
cout<<top->_value<<" ";
prev = top;//将prev更新为已经访问的节点
s.pop();
}
else//以子问题的方式去访问右子树
{
cur = top->_rchild;
}
}
cout<<endl;
}
void levelorder(Node* root)//层序遍历打印树的各个节点
{
queue<Node*> q;
if (root)
{
q.push(root);//将根节点插进队列
}
while(!q.empty())
{
Node* front = q.front();
q.pop();
cout<<front->_value<<" ";
if (front->_lchild)
{
q.push(front->_lchild);
}
if (front->_rchild)
{
q.push(front->_rchild);
}
}
}
3、求二叉树的高度(深度)
外部接口:
size_t Depth()//求树的深度
{
cout<<"depth:";
return depth(_root);
}
算法实现:
size_t depth(Node* root)//求树的深度
{
if (NULL == root)
{
return 0;
}
else
{
return depth(root->_lchild)>depth(root->_rchild)?
(depth(root->_lchild)+1):(depth(root->_rchild)+1);
}
}
4、求二叉树中节点的个数
外部接口:
size_t Size()//求树中的节点个数
{
cout<<"size:";
return size(_root);
}
算法实现:
size_t size(Node* root)//求树中的节点个数
{
size_t count = 0;
if (NULL == root)
{
count = 0;
}
else
{
//当前节点 = 左子树节点 + 右子树节点 + 1
count = size(root->_lchild) + size(root->_rchild)+ 1;
}
return count;
}
5、求二叉树中叶子节点的个数
外部接口:
size_t GetLeafSize()
{
cout<<"leaf_size:";
return getleafsize(_root);
}
算法实现:
size_t getleafsize(Node* root)//求叶节点的个数
{
if (NULL == root)//空树
{
return 0;
}
if (NULL == root->_lchild && NULL == root->_rchild)//左叶节点右节点均为空,即
{
return 1;
}
else//左子树的叶节点+右子树的叶节点
{
return getleafsize(root->_lchild)+getleafsize(root->_rchild);
}
}
6、求二叉树第K层的节点个数(假设根节点为第1层)
外部接口:
size_t GetKLevelSize(size_t k)//树中第k层的节点个数
{
cout<<k<<"_level_size:";
return getklevelsize(_root,k);
}
算法实现:
size_t getklevelsize(Node* root,size_t k)//树中第k层的节点个数
{
assert(k>0);
size_t count = 0;
if (NULL == root)
{
return 0;
}
if (k == 1)
{
count++;
}
else
{
count = getklevelsize(root->_lchild,k-1)
+ getklevelsize(root->_rchild,k-1);
}
return count;
}
7、判断一个节点是否在二叉树中
外部接口:
bool Find(const T& data) //判断一个值为的节点是否在当前二叉树中
{
return _Find(_root,data);
}
算法实现:
bool _Find(Node* root,const T& data) //查找指定数据的节点
{
if (NULL == root)
{
return false;
}
else if (root->_data == data)
{
return true;
}
else
{
bool isIn = false;
if (root->_left && !isIn)
isIn = _Find(root->_left,data);
if (root->_right && !isIn)
isIn = _Find(root->_right,data);
return isIn;
}
}
8、求两个节点的最近公共祖先
外部接口:
Node* sameAncester(Node* n1,Node* n2) //求两个节点的最近公共祖先
{
//return _sameAncester1(_root,n1,n2);
//return _sameAncester2(_root,n1,n2);
return _sameAncester3(_root,n1,n2);
}
算法实现:
Node* _sameAncester1(Node*& root,Node* n1,Node* n2)//求两个节点的最近公共祖先(时间复杂度:O(N^2))
{
assert(n1 && n2);
//保证两个节点都在当前树中,且当前树不为空
if (root && Find(n1->_data) && Find(n2->_data))
{
//其中一个节点为根节点,直接返回根节点
if (root->_data == n1->_data || root->_data == n2->_data)
{
return root;
}
//两个节点:1、都在左子树 2、都在右子树 3、一个在左子树,一个在右子树
else
{
Node* cur = root;
//在当前树的左子树去找n1和n2节点,如果没找到就一定在右子树
bool tmp1 = _Find(cur->_left,n1->_data);
bool tmp2 = _Find(cur->_left,n2->_data);
//n1和n2都在左子树
if (tmp1 && tmp2)
{
return _sameAncester1(cur->_left,n1,n2);
}
//n1和n2都在右子树
else if(!tmp1 && !tmp2)
{
return _sameAncester1(cur->_right,n1,n2);
}
//n1与n2一个在左子树一个在右子树
else
{
return root;
}
}
}
return NULL;
}
bool _GetPath2(Node* root,Node* cur,stack<Node*>& s)//在根为root的树中查找节点cur的路径
{
if (NULL == root || NULL == cur)//树为空
return false;
s.push(root); //将当前节点入栈
if (cur->_data == root->_data) //找到路径
{
return true;
}
if (_GetPath2(root->_left,cur,s))//在左子树中找路径
{
return true;
}
if (_GetPath2(root->_right,cur,s))//在右子树中找路径
{
return true;
}
s.pop();
return false;
}
Node* _sameAncester2(Node*& root,Node* n1,Node* n2)//求两个节点的最近公共祖先(时间复杂度:O(N))
{
//树为空
if (NULL == root)
{
return NULL;
}
stack<Node*> s1;
stack<Node*> s2;
//用栈s1和s2分别保存节点n1和节点n2的路径
_GetPath2(root,n1,s1);
_GetPath2(root,n2,s2);
//将多余的路径删除
while (s1.size() > s2.size())
{
s1.pop();
}
while(s1.size() < s2.size())
{
s2.pop();
}
//找s1与s2中不同的节点
while(s1.top() != s2.top())
{
s1.pop();
s2.pop();
}
return s1.top();
}
bool _GetPath3(Node* root,Node* cur,vector<Node*>& v)
{
if (NULL == root || NULL == cur)//树为空
return false;
v.push_back(root);
if (cur->_data == root->_data) //找到路径
{
return true;
}
else
{
bool ret = false;
if (root->_left && !ret)//在左子树中找路径
{
ret = _GetPath3(root->_left,cur,v);
}
if (root->_right && !ret)//在右子树中找路径
{
ret = _GetPath3(root->_right,cur,v);
}
if (ret == false && v.size() != 0)//如果不是正确路径上的节点,就去除该节点
{
v.pop_back();
}
return ret;
}
}
Node* _sameAncester3(Node*& root,Node* n1,Node* n2)
{
//树为空
if (NULL == root)
{
return NULL;
}
//定义容器
vector<Node*> v1;
vector<Node*> v2;
//用容器v1和v2分别保存节点n1和节点n2的路径
_GetPath3(root,n1,v1);
_GetPath3(root,n2,v2);
////从下往上找
////定义指向查找节点的迭代器指针
//vector<Node*>::iterator it1 = --v1.end();
//vector<Node*>::iterator it2 = --v2.end();
////将多余的路径节点删除掉
//while(v1.size() > v2.size())
//{
// --it1; //相应的迭代器要向前移
// v1.pop_back();
//}
//while(v2.size() > v1.size())
//{
// --it2; //相应的迭代器要向前移
// v2.pop_back();
//}
////找v1与v2中不同的节点
//while(it1 >= v1.begin() && it2 >= v2.begin())
//{
// if (*it1 == *it2)
// {
// return *it1;
// }
// --it1;
// --it2;
//}
//从上往下找
//定义指向查找节点的迭代器指针
vector<Node*>::iterator it1 = v1.begin();
vector<Node*>::iterator it2 = v2.begin();
//找v1与v2中不同的节点
while(it1 != v1.end() && it2 != v2.end())
{
if (*it1 != *it2)//找到第一个不相同的节点,返回前一个节点
{
return *(--it1);
}
++it1;
++it2;
}
//处理特殊情况,例如:v1:1->2->3 v2:1->2
if (it1 == v1.end() || it2 == v2.end())
return *(--it1);
return NULL;
}
9、判断一棵二叉树是否是平衡二叉树
算法实现
bool IsCompleteBT()//判断一棵树是否是完全二叉树
{
bool flag = true;//判断一棵子树是否为一棵完全二叉树的标记
queue<Node*> q; //运用队列进行层序遍历
q.push(_root);
while(!q.empty())
{
Node* front = q.front();//保存队首节点
q.pop(); //将队首元素出队
/*如果队首元素的左树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/
if (NULL == front->_left)
{
flag = false;
}
else
{
/*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的左孩子不为空,说明不是完全二叉树*/
if (flag == false)
{
return false;
}
q.push(front->_left);//继续向后探索
}
/*如果队首元素的右树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/
if (NULL == front->_right)
{
flag = false;
}
else
{
/*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的右孩子不为空,说明不是完全二叉树*/
if (flag == false)
{
return false;
}
q.push(front->_right);//继续向后探索
}
}
return true;//能走到这里说明一定是完全二叉树
}
10、求二叉树中最远的两个节点的距离
外部接口:
int RemoteDistance() //求二叉树中最远的两个节点的距离
{
int tmp = 0;
return _RemoteDistance1(_root,tmp);
}
算法实现:
int _RemoteDistance1(Node* root,int& distance)
{
if (NULL == root)
{
return 0;
}
int tmp = _Depth(root->_left)+_Depth(root->_right);//递归去求每棵子树的最远距离
if (distance < tmp)//用全局变量保存每棵子树的最远距离,并不断更新
{
distance = tmp;
}
return distance;
}
11、由前序遍历和中序遍历重建二叉树
外部接口:
Node* Rebuild(T* prevOrder,T* inOrder,int n) //根据前序序列和中序序列重建二叉树
{
assert(prevOrder && inOrder);
int prevIndex = 0; //指向前序序列指针的下标
int inBegin = 0; //中序序列的首位置的下标
int inEnd = n-1; //中序序列的末位置的下标(闭区间)
_root = _Rebuild(prevOrder,prevIndex,inOrder,inBegin,inEnd,n);
return _root;
}
算法实现:
Node* _Rebuild(T* prevOrder,int& prevIndex,T* inOrder,int inBegin,int inEnd,int n)
{
Node* root = NULL;
if (prevIndex < n)//当前序序列的下标<n时才创建新结点
{
root = new Node(prevOrder[prevIndex]);
}
if (NULL == root)//递归的返回条件
{
return NULL;
}
if (inBegin == inEnd)//要重建树中只有一个节点
{
return root;
}
else
{
//在中序序列中找到前序序列中的根
int i = inBegin;
for ( ; i <= inEnd; ++i)
{
if (prevOrder[prevIndex] == inOrder[i])
break;
}
if (i > inEnd)//说明中序序列中没找到前序序列中的根,所给序列不匹配
{
throw invalid_argument("所给序列不匹配!");//直接抛异常
}
//将根节点作为分割线,将区间分为左右两部分,分别递归重建左右子树
root->_left = _Rebuild(prevOrder,++prevIndex,inOrder,inBegin,i-1,n);
root->_right = _Rebuild(prevOrder,++prevIndex,inOrder,i+1,inEnd,n);
return root;
}
}
12、判断一棵树是否是完全二叉树
算法实现:
bool IsCompleteBT()//判断一棵树是否是完全二叉树
{
bool flag = true;//判断一棵子树是否为一棵完全二叉树的标记
queue<Node*> q; //运用队列进行层序遍历
q.push(_root);
while(!q.empty())
{
Node* front = q.front();//保存队首节点
q.pop(); //将队首元素出队
/*如果队首元素的左树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/
if (NULL == front->_left)
{
flag = false;
}
else
{
/*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的左孩子不为空,说明不是完全二叉树*/
if (flag == false)
{
return false;
}
q.push(front->_left);//继续向后探索
}
/*如果队首元素的右树为空,将标志置为false,如果层序遍历\ \后面的节点还有子树,说明不是完全二叉树*/
if (NULL == front->_right)
{
flag = false;
}
else
{
/*如果flag为假,说明之前已经有节点的孩子为空,又因当前\ \节点的右孩子不为空,说明不是完全二叉树*/
if (flag == false)
{
return false;
}
q.push(front->_right);//继续向后探索
}
}
return true;//能走到这里说明一定是完全二叉树
}
13、求二叉树的镜像
外部接口:
void GetMirrorTree()//求二叉树的镜像
{
_GetMirrorTree(_root);
}
算法实现:
void _GetMirrorTree(Node* root)//求二叉树的镜像
{
if (NULL == root)
{
return;
}
swap(root->_left,root->_right);//交换左右子树
_GetMirrorTree(root->_left); //递归遍历左子树
_GetMirrorTree(root->_right);//递归遍历右子树
}
14、将二叉搜索树转换为一个已排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向
外部接口:
void MakeSearchToList()/*将二叉搜索树转换成一个排序的双向链表。\ 要求不能创建任何新的结点,只能调整树中结点指针的指向。*/
{
Node* cur = _root;
//寻找最左节点
while (cur->_left)
{
cur = cur->_left;
}
Node* prev = NULL;
_MakeSearchToList1(_root,prev);
//_MakeSearchToList2(_root);
while(cur)//打印链表(树的结构变了之后析构会死循环)
{
cout<<cur->_data<<"<->";
cur = cur->_right;
}
cout<<"null";
}
算法实现:
void _MakeSearchToList1(Node* root,Node*& prev)
{
if (NULL == root)//如果当前树为空,则直接返回
return;
_MakeSearchToList1(root->_left,prev);//递归左子树
//进行转换
root->_left = prev;//root第一次指向最左节点
if (prev)//如果上一个节点不为空,就将上一个节点的右指针指向当前节点root
{
prev->_right = root;
}
prev = root;//更新保存上一个节点
_MakeSearchToList1(root->_right,prev);//递归右子树
}
void _InOrderList(Node* root,queue<Node*>& q)//将树的中序遍历的节点入到队列中
{
if (NULL == root)
return;
_InOrderList(root->_left,q);//递归左子树
q.push(root); //将当前节点入队
_InOrderList(root->_right,q);//递归右子树
}
void _MakeSearchToList2(Node* root)
{
if (NULL == root)//如果当前树为空则直接返回
return;
queue<Node*> q;
_InOrderList(root,q);//将所给树的中序序列push到队列q中
Node* head = q.front();//将队首节点(即所给树的最左节点)保存起来
q.pop();
head->_left = NULL;//将队首节点的左指针置空
Node* prev = head;//用prev保存当前节点
while(!q.empty())//当队列不空时进入循环
{
Node* front = q.front();//获取队首节点
q.pop(); //将队首节点出队
prev->_right = front; //将前一个节点的右指针指向队首节点
front->_left = prev; //将队首节点的左指针指向上一个节点
front->_right = NULL; //将队首节点的右指针置空
prev = front; //更新保存上一个节点
}
}