二叉树是一种很重要的数据结构,所以了解并掌握它的一些特点尤其重要。本文介绍二叉树的一些特点,并用C++实现其创建与几种遍历方式。
二叉树的特点:
- 在二叉树的第i层至多有2^(i-1)个结点(i >= 1)
- k层二叉树至多有2^k - 1个结点
- 如果二叉树度为2的节点为n,则其叶子结点数为n+1
- 具有n个节点的完全二叉树的深度为[log 2 n] + 1([x]表示不超过x的最大整数)
二叉树也有顺序存储结构和链式存储结构,但是习惯上用链式存储来表示二叉树,也就是常说的二叉链表。
二叉链表的节点定义:
template<class T>
struct BTreeNode
{
T data;
struct BTreeNode<T> *lchild, *rchild;
};
二叉树的基本操作(BTree.h):
/*
二叉树(二叉链表)的C++实现
phlixce 2017.8.26
*/
#ifndef BTREE_H
#define BTREE_H
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
//二叉树的节点,包括一个data,左孩子,右孩子
//左右孩子采用递归定义的方式
template<class T>
struct BTreeNode
{
T data;
struct BTreeNode<T> *lchild, *rchild;
};
//二叉链表
template<class T>
class BTree
{
public:
BTree(); //二叉树构造函数,调用私有成员CreateBTree()实现
BTreeNode<T>* GetRoot(); //返回二叉树的根节点,根节点指向一个二叉链表,所以其返回值就是由多个二叉树节点组成的链表。
void PreOrder(); //二叉树前序遍历,使用非递归方式实现(借助于栈)
void InOrder(); //二叉树中序遍历,使用非递归方式实现(借助于栈)
void PostOrder(); //二叉树后序遍历,使用非递归方式实现(借助于栈)
void LevelOrder(); //二叉树前序遍历,使用非递归方式实现(借助于队列)
//递归与非递归使用函数重载实现(输入参数不同)
void PreOrder(BTreeNode<T>* node); //二叉树前序遍历,递归实现,所以需要传入根节点。
void InOrder(BTreeNode<T>* node); //二叉树中序遍历,递归实现,所以需要传入根节点。
void PostOrder(BTreeNode<T>* node); //二叉树后序遍历,递归实现,所以需要传入根节点。
int BTreeSize(BTreeNode<T>* node); //返回二叉树的节点个数,递归实现
int BTreeLeaves(BTreeNode<T>* node); //返回二叉树的叶子节点个数,递归实现
private:
BTreeNode<T>* root; //二叉树根节点,节点指向一个二叉链表
BTreeNode<T>* CreateBTree(); //二叉树的创建,由用户输入实现
};
template<class T>
BTree<T>::BTree()
{
cout << "请按前序遍历的方式输入二叉树,叶子节点用#代替!" << endl;
root = new BTreeNode<T>;
root = CreateBTree();
}
template<class T>
BTreeNode<T>* BTree<T>::GetRoot()
{
return root;
}
//注意该创建思路是根据前序遍历的思路创建的,所以输入的时候要按前序遍历的顺序输入,并且每个叶子后面都要跟两个#。这样才能输入结束。
template<class T>
BTreeNode<T>* BTree<T>::CreateBTree()
{
T input;
cin >> input;
BTreeNode<T>* node;
if(input == '#')
{
node = NULL; //输入为‘#’,就使当前节点为空
}
else
{
node = new BTreeNode<T>;
node->data = input; //赋值给当前节点
node->lchild = CreateBTree(); //创建左孩子
node->rchild = CreateBTree(); //创建右孩子
}
return node; //将二叉链表node返回给root
}
template<class T>
void BTree<T>::PreOrder(BTreeNode<T>* node)
{
if(NULL == node)
{
//cout << "二叉树是一棵空树" << endl;
}
else
{
cout << node->data << " "; //前序,先输出根节点
PreOrder(node->lchild); //输出左孩子
PreOrder(node->rchild); //输出右孩子
}
//cout << endl;
}
template<class T>
void BTree<T>::InOrder(BTreeNode<T>* node)
{
if(NULL == node)
{
//cout << "二叉树是一棵空树" << endl;
}
else
{
InOrder(node->lchild); //先找到左孩子输出
cout << node->data << " "; //输出根节点
InOrder(node->rchild); //最后输出右孩子
}
//cout << endl;
}
template <class T>
void BTree<T>::PostOrder(BTreeNode<T>* node)
{
if(NULL == node)
{
//cout << "二叉树是一棵空树" << endl;
}
else
{
PostOrder(node->lchild); //先输出左孩子
PostOrder(node->rchild); //再输出右孩子
cout << node->data << " "; //最后输出根节点
}
//cout << endl;
}
/*
层序遍历:以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则其层序遍历为1 2 3 4 5 6 7
第一次while:s指向1,将1出队列,输出1,1的左右孩子都存在,所以推入左右孩子2和3,队列为3 2(2代表队头,3代表队尾);
第二次while:s指向2,将2出队列,输出2,2的左右孩子都存在,所以推入左右孩子4和5,队列为5 4 3;
第三次while:s指向3,将3出队列,输出3,3的左右孩子都存在,所以推入左右孩子6和7,队列为7 6 5 4;
第四次while:s指向4,将4出队列,输出4,4的左右孩子均不存在,所以剩下队列为7 6 5;
第五次while:s指向5,将5出队列,输出5,5的左右孩子均不存在,所以剩下队列为7 6;
第六次while:s指向6,将6出队列,输出6,6的左右孩子均不存在,所以剩下队列为7;
第七次while:s指向7,将7出队列,输出7,7的左右孩子均不存在,所以剩下队列为空;
下一次队列为空,循环结束。
输出结果 1 2 3 4 5 6 7
*/
template <class T>
void BTree<T>::LevelOrder()
{
queue<BTreeNode<T>*> q; //创建一个队列,队列中的每个元素都是一个二叉链表
BTreeNode<T>* s = root; //二叉链表s指向root
if(NULL == s)
{
return; //如果原二叉树为空,返回
}
q.push(s); //将根节点推入队列
while(!q.empty())
{
s = q.front(); //s指向队列的第一个元素,第一次即指向root
q.pop(); //将队列第一个元素弹出,
cout << s->data << " ";
if(s->lchild)
{
q.push(s->lchild); //左孩子存在,推入左孩子
}
if(s->rchild)
{
q.push(s->rchild); //右孩子存在,推入右孩子
}
}
}
/*
前序遍历(非递归):以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则其前序遍历为1 2 4 5 3 6 7。
进入while前:p指向root,栈为空。
第一次while:
while:p指向root,不为NULL,输出root的值1,将1压入栈中,p指向左孩子2;循环,输出2,压入2,指向4;输出4,压入4,指向4的左孩子NULL,while结束。
if: 由于栈不空,p指向4,出栈4,p指向4的右孩子NULL。
结果: 输出 1 2 4,栈中元素为1 2(1为栈低,2为栈顶)
第二次while:
while:p为空,不执行。
if :p指向2,出栈2,p指向2的右孩子5。
结果 :无输出,栈中元素为1
第三次while:
while:p指向5,不空,输出5,将5压栈,p指向5的左孩子NULL,while结束。
if:p指向5,出栈5,p指向5的右孩子NULL。
结果:输出5,进栈出栈5,栈元素为1.
第四次while:
while:p指向NULL,不执行。
if:p指向1,出栈1,p指向1的右孩子3。
结果:无输出,栈元素为空。
第五次while:(p不为空)
while:p指向3,不空,输出3,压入3,指向3的左孩子6;输出6,压入6,指向6的左孩子NULL,结束。
if:p指向6,出栈6,p指向6的右孩子NULL。
结果:输出 3 6,栈元素为3。
第六次while:
while:p指向NULL,不执行。
if:p指向3,出栈3,p指向3的右孩子7。
结果:无输出,栈元素为空。
第七次while:
while:p不为空,输出7,压入7,p指向7的左孩子NULL。
if:p指向7,出栈7,p指向7的右孩子NULL。
结果:输出7,压栈出栈7,栈元素为空。
下一次p指向空,栈为空,循环结束。
输出结果 1 2 4 5 3 6 7
*/
template<class T>
void BTree<T>::PreOrder()
{
stack<BTreeNode<T>*> s;
BTreeNode<T>* p = root;
if(NULL == p)
{
return;
}
while(p != NULL || !s.empty())
{
while(p != NULL)
{
cout << p->data <<" ";
s.push(p);
p = p->lchild;
}
if(!s.empty())
{
p = s.top();
s.pop();
p = p->rchild;
}
}
}
/*
中序遍历(非递归):以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则中序遍历为4 2 5 1 6 3 7。
进入while前:p指向root,栈为空。
第一次while:
while:p指向root,不为NULL;将1压入栈中,p指向左孩子2;循环,压入2,指向4;压入4,指向4的左孩子NULL,while结束。
if:由于栈不空,p指向4,输出4,出栈4,p指向4的右孩子NULL。
结果: 输出4,栈中元素为1 2(1为栈低,2为栈顶)
第二次while:
while:p为空,不执行。
if :p指向2,输出2,出栈2,p指向2的右孩子5。
结果 :输出2,栈中元素为1
第三次while:
while:p指向5,不空;将5压栈,p指向5的左孩子NULL,while结束。
if:p指向5,输出5,出栈5,p指向5的右孩子NULL。
结果:输出5,进栈出栈5,栈元素为1.
第四次while:
while:p指向NULL,不执行。
if:p指向1,输出1,出栈1,p指向1的右孩子3。
结果:输出1,栈元素为空。
第五次while:(p不为空)
while:p指向3,不空;压入3,指向3的左孩子6;输出6,压入6,指向6的左孩子NULL,结束。
if:p指向6,输出6,出栈6,p指向6的右孩子NULL。
结果:输出6,栈元素为3。
第六次while:
while:p指向NULL,不执行。
if:p指向3,输出3,出栈3,p指向3的右孩子7。
结果:输出3,栈元素为空。
第七次while:
while:p不为空,压入7,p指向7的左孩子NULL。
if:p指向7,输出7,出栈7,p指向7的右孩子NULL。
结果:输出7,压栈出栈7,栈元素为空。
下一次p指向空,栈为空,循环结束。
输出结果,4 2 5 1 6 3 7
*/
template <class T>
void BTree<T>::InOrder()
{
stack<BTreeNode<T>*> s; //创建一个元素为二叉链表的栈
BTreeNode<T>* p = root;
if(NULL == p)
{
return; //如果root为空,返回
}
while(p != NULL || !s.empty())
{
while(p != NULL) //当前节点不为空
{
s.push(p); //将当前节点压栈
p = p->lchild; //循环将左孩子压栈
}
if(!s.empty()) //执行到此,说明左斜树已经全部压入栈中
{
p = s.top(); //指向栈顶元素
cout << p->data << " "; //输出左孩子
s.pop(); //将栈顶元素出栈
p = p->rchild; //节点指向当前节点的右孩子
}
}
}
/*
后序遍历(非递归):以输入1 2 4 # # 5 # # 3 6 # # 7 # #为例,则后层序遍历为4 5 2 6 7 3 1。
进入while前:p指向root,栈中含有一个元素root,pre=NULL,cur未初始化。
第一次while:
cur指向1,cur左右孩子均不为空,pre为空,所以if语句不执行;执行else压入右孩子3和左孩子2。
结果:无输出,栈中元素为1 3 2(3为栈底,2为栈顶)
第二次while:
cur指向2,2左右孩子不为空,pre=NULL,if语句不执行;执行else压入右孩子5与左孩子4.
结果:无输出,栈中元素为1 3 2 5 4
第三次while:
cur指向4,cur左右孩子均为空,执行if,输出4,出栈4,pre指向4。
结果:输出4,栈中元素为1 3 2 5
第四次while:
cur指向5,cur左右孩子均为空,执行if,输出5,出栈5,pre指向5。
结果:输出5,栈中元素为1 3 2
第五次while:(p不为空)
cur指向2,2左右孩子不为空,pre不为空,pre是cur的右孩子,执行if,输出2,出栈2,pre指向2。
结果:输出2,栈中元素为1 3。
第六次while:
cur指向3,3的左右孩子均不为空,pre虽不为空,但是pre(2)既不是3的左孩子也不是右孩子,if不执行,执行else,压入3的右孩子7与左孩子6。
结果:无输出,栈中元素为1 3 7 6。
第七次while:
cur指向6,执行if,输出6,出栈6,pre指向6。
结果:输出6,栈中元素为1 3 7。
第八次while:
cur指向7,执行if,输出7,出栈7,pre指向7。
结果:输出7,栈中元素为1 3。
第九次while:
cur指向3,pre不为空,pre是cur的右孩子,执行if,输出3,出栈3,pre指向3。
结果:输出3,栈中元素为1。
第十次while:
cur指向1,pre不空,pre是cur的右孩子,指向if,输出1,出栈1,pre指向1。
结果:输出1,栈中元素为空。
下一次栈为空,循环结束。
输出结果,4 5 2 6 7 3 1
*/
template <class T>
void BTree<T>::PostOrder()
{
stack<BTreeNode<T>*> s;
BTreeNode<T>* p = root;
if(NULL == p)
{
return;
}
s.push(p);
BTreeNode<T>* pre = NULL; //上一个访问的节点
BTreeNode<T>* cur; //当前节点
while(!s.empty())
{
cur = s.top();
//当前节点左右孩子均为空
//或者上一次访问的节点是此次节点的左孩子或者右孩子,但是已经被访问过了
//直接输出,然后出栈
if((cur->lchild == NULL && cur->rchild == NULL) || ((pre != NULL) && (pre == cur->lchild || pre == cur->rchild)))
{
cout << cur->data << " ";
s.pop();
pre = cur;
}
else
{
if(cur->rchild)
{
s.push(cur->rchild);
}
if(cur->lchild)
{
s.push(cur->lchild);
}
}
}
}
//二叉树节点个数
template<class T>
int BTree<T>::BTreeSize(BTreeNode<T>* node)
{
BTreeNode<T>* p = node;
if(NULL == p)
{
return 0; //空树,返回0。
}
else
{
return 1+BTreeSize(p->lchild) + BTreeSize(p->rchild); //非空,返回左子树节点数+右子树结点数+根节点数1
}
}
//二叉树叶子节点个数
template <class T>
int BTree<T>::BTreeLeaves(BTreeNode<T>* node)
{
BTreeNode<T>* p = node;
if(NULL == p)
{
return 0; //空树返回0
}
else if(p->lchild == NULL && p->rchild == NULL)
{
return 1; //节点的左右孩子均为NULL,则加1
}
else
{
return BTreeLeaves(p->lchild) + BTreeLeaves(p->rchild); //返回左子树的叶子节点数+右子树的叶子节点
}
}
#endif
#include "BTree.h"
#include <iostream>
using namespace std;
int main()
{
BTree<char> my_btree;
cout << "--------二叉树前序遍历(递归)--------" << endl;
my_btree.PreOrder(my_btree.GetRoot());
cout << endl << endl;
cout << "--------二叉树中序遍历(递归)--------" << endl;
my_btree.InOrder(my_btree.GetRoot());
cout << endl << endl;
cout << "--------二叉树后序遍历(递归)--------" << endl;
my_btree.PostOrder(my_btree.GetRoot());
cout << endl << endl;
cout << "--------二叉树层序遍历(非递归)--------" << endl;
my_btree.LevelOrder();
cout << endl << endl;
cout << "--------二叉树前序遍历(非递归)--------" << endl;
my_btree.PreOrder();
cout << endl << endl;
cout << "--------二叉树中序遍历(非递归)--------" << endl;
my_btree.InOrder();
cout << endl << endl;
cout << "--------二叉树后序遍历(非递归)--------" << endl;
my_btree.PostOrder();
cout << endl << endl;
cout << "--------二叉树节点数--------" << endl;
int size = my_btree.BTreeSize(my_btree.GetRoot());
cout << size << endl << endl;
cout << "--------二叉树叶节点数--------" << endl;
int leaves = my_btree.BTreeLeaves(my_btree.GetRoot());
cout << leaves << endl << endl;
system("pause");
return 0;
}
遍历方式的递归过程比较简单,但是非递归有点麻烦,所以写了一些注解,水平有限,如有错误,望见谅。在编写过程中本想写一个BTree.cpp实现类中的函数,而不是放在同一个头文件中,可是VS总是报error Link2019 无法解析的外部符号,不知各位有什么解决办法。
参考链接:
http://blog.csdn.net/iqrocket/article/details/8266365
http://blog.csdn.net/pi9nc/article/details/13008511/