二叉树的创建
定义:
二叉树是每个节点最多有两个子树的树结构。通常子树被称为”左子树”和”右子树”。二叉树的每个节点最多只能右两棵子树,子树有左右之分,次序不能颠倒。
概念:
1.满二叉树:
除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
2.完全二叉树:
假设二叉树的高度为K,除第K层外,其他各层的节点数都达到最大个数。也就是第一层到第K-1层为一个满二叉树。第K层有叶子节点,并且叶子结点都是从左到右依次排列。
3.平衡二叉树:
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
创建
模型:
假如我们要创建的二叉树是这样的
在这里给出一个数组:
arr[] = { 1 , 2 , 3 ,’#’,’#’,4,’#’,’#’, 5 , 6 }
我们使用递归从根节点开始排列,然后放入它的左节点,再将它的左节点当作根节点依次排列,当碰到’#’时,则置为 NULL并返回到上一层节点,这时处理该节点的右节点,如果碰到’#’置NULL并返回,一直重复进行,直到读完本数组。
实现:
template<class T>
struct BinaryTreeNode
{
public:
BinaryTreeNode()
:_data(NULL)
{}
BinaryTreeNode(T &t)
{
_data = t;
}
public:
T _data;
BinaryTreeNode<T> *leftChild; //左节点
BinaryTreeNode<T> *rightChild; //右节点
};
// 1 2 3 # # 4 # # 5 6
template<class T>
class BinaryTree
{
typedef BinaryTreeNode<T> Node;
public:
BinaryTree()
:_root(NULL)
{}
BinaryTree(T* a, size_t n,const T &invalid)
{
size_t index = 0; //从根节点开始传入
_root = CheckTree(a, n, invalid, index); // m 数组大小
//invalid非法值 index 构建的位置
}
protected:
Node * CheckTree(T *a, size_t n, const T& invalid, size_t &index) { //二叉树初始化
Node *t = NULL;
if (a[index] != invalid && index < n) { // 如果不是#
//或者未到达边界
t = new Node(a[index]);
t->leftChild = CheckTree(a, n, invalid, ++index);
//处理左子树
t->rightChild = CheckTree(a, n, invalid, ++index);
//处理右子树
}
return t;
}
private:
Node* _root;
};
遍历
遍历顺序
1.前序遍历
递归实现
首先访问根,再前序遍历左子树,最后前序遍历右子树
public:
//4种遍历
void PrevOrder() //嵌套达成封装
{
_PrevOrder(_root);
cout << endl;
}
private:
void _PrevOrder(Node *root) //前序遍历
{
if (root == NULL) { //如果根节点为空,则不访问
return;
}
cout << root->_data<<" "; //先输出根节点
_PrevOrder(root->leftChild); //左节点
_PrevOrder(root->rightChild); //右节点
}
非递归实现
先压入根节点,并打印根节点,然后访问根节点的左节点,如果不为空,则打印左节点,再访问左节点的左节点。一直循环下去,直到下一左节点为空,则应该进行出栈并访问该节点的右节点。如果该节点的右节点为空,则应该再次出栈访问该节点的上一节点的右子树。依次循环。
实现:
TailPrint(Node *head){
stack<Node *> s;
Node *cur = _root;
while(!s.empty()||cur)
{
//1.访问左子树并且压栈
while(cur) 1 2 cur=null
{
s.push(cur);
cout<<cur->_data<<" ";
cur=cur->leftChild;
}
//2.取栈里面节点的右子树 如果右子树未访问。
Node * top=s.top(); 2
s.pop();
cur=top->rightChild;
}
}
2.中序遍历
递归实现
首先中序遍历左子树,再访问根,最后中序遍历右子树。
public:
void _In0rder() //嵌套实现封装
{
InOrder(_root);
cout << endl;
}
private:
void InOrder(Node *root){ //中序遍历
if (root == NULL) { //如果根节点为空,则不访问
return;
}
InOrder(root->leftChild);
cout << root->_data << " ";
InOrder(root->rightChild);
}
非递归实现
中序遍历与前序类似,是先访问所有的左节点但是不打印,直到左节点为空是表明到达左子树的下面,这时需要打印出栈并访问当前节点的右子树,如果右为空就出栈,访问上一节点的右子树,如果不为空就继续遍历。
void InPrint()
{
if (_root == NULL)
return;
//树非空
Node* cur = _root;
stack<Node*> In;
while (!In.empty() || cur)
{
//一直遍历到左子树最下边,边遍历边保存根节点到栈中
while (cur)
{
In.push(cur);
cur = cur->leftChild;
}
//当为空时,说明已经到达左子树最下边,这时需要出栈了
if (!In.empty())
{
cur = In.top();
In.pop();
cout << " " << cur->_data;
//进入右子树,开始新的一轮左子树遍历(这是递归的自我实现)
cur = cur->rightChild;
}
}
}
3.后序遍历
递归遍历
首先后序遍历左子树,再后序遍历右子树,最后访问根。
public:
void _PostOrder() //嵌套实现封装
{
PostOrder(_root);
}
private:
void PostOrder(Node *root) //后序遍历
{
if (root == NULL) { //如果根节点为空,则不访问
return;
}
PostOrder(root->leftChild);
PostOrder(root->rightChild);
cout << root->_data<<" ";
}
非递归遍历
后序遍历也是先访问到左子树的最下面,访问之后应该先访问右节点再访问根节点,而访问根节点的前提是,根节点无右子树或右子树已经被访问过,所以需要一个点用来保存访问过的右子树。进入右子树,若右子树不为空,则开始遍历右子树的左节点,依次循环遍历。
void LastPrint()
{
if (_root == NULL)
return;
stack<Node *> Last;
//cur:当前访问节点,pLast:上次访问节点
Node* cur, *pLast;
cur = _root;
pLast = NULL;
//先把cur移动到左子树最下边
while (cur)
{
Last.push(cur);
cur = cur->leftChild;
}
while (!Last.empty())
{
//走到这里,cur都是空,并已经遍历到左子树底端
cur = Last.top();
Last.pop();
//一个根节点被访问的前提是:无右子树或右子树已被访问过
if (cur->rightChild == NULL || cur->rightChild == pLast)
{
cout << cur->_data<<" ";
//修改最近被访问的节点
pLast = cur;
}
//若左子树刚被访问过,则需先进入右子树(根节点需再次入栈)
else
{
//根节点再次入栈
Last.push(cur);
//进入右子树
cur = cur->rightChild;
//如果右子树不为空,访问右子树的所有左节点
while (cur)
{
Last.push(cur);
cur = cur->leftChild;
}
}
}
cout << endl;
}
4.层序遍历:
即按照层次访问,访问根,访问子女,再访问子女的子女。
此时用递归发现会很难实现,便改用循环和队列实现层序遍历。
方法:
创建一个队列,先将根节点入队,进入循环,将队头的数据打印并出队,如果左节点不为空,将左节点入队,如果右节点不为空,将右节点入队。
重复循环内的操作。
实现:
void LevelOrder(Node *root) //利用队列
{
queue<Node*> buf; //定义一个队列 保存节点该数据
buf.push(root); //保存根节点
while (!buf.empty()) //不为空时
{
Node * front = buf.front(); // 保存并打印最前面的节点
cout << front->_data << " ";
buf.pop(); //删除最前面的节点
if (front->leftChild != NULL) {
buf.push(front->leftChild); //先判断左节点,压入左节点
}
if (front->rightChild != NULL) { //判断右节点,压入右节点
buf.push(front->rightChild);
}
}
}