二叉树:二叉树是一棵特殊的树,二叉树每个节点最多有两个孩子结点,分别称为左孩子和右孩子。
满二叉树:高度为N的满二叉树有2^N- 1个节点的二叉树。
完全二叉树: 若设二叉树的深度为h,除第h 层外,其它各层(1~h-1) 的结点数都达到最大个数,第h 层所有的结点都连续集中在最左边,这就是完全二叉树
二叉树的基本操作:构造二叉树,前序遍历,中序遍历,后序遍历,求树的叶子结点,树的深度,树中第K层的结点个数,树中结点的个数,以及在树中查找一个结点。
前序遍历(先根遍历):(1):先访问根节点; (2):前序访问左子树;(3):前序访问右子树; 【1 2 3 4 5 6】
中序遍历: (1):中序访问左子树;(2):访问根节点; (3):中序访问右子树; 【3 2 4 1 6 5】
后序遍历(后根遍历):(1):后序访问左子树;(2):后序访问右子树;(3):访问根节点; 【3 4 2 6 5 1】
层序遍历: (1):一层层节点依次遍历。 【1 2 5 3 4 6】
注意
我们二叉树的前中后序三种遍历可以用递归和非递归来实现,用递归实现简单但是他也是有缺陷的,它的缺陷是递归每次需要压栈,而栈的空间相对来说不是很大,在多线程中只有十几兆,如果在极端情况下这个二叉树深度很大,则会有栈溢出的问题产生,所以本文给大家介绍递归与非递归两种方法
我们先定义一个二叉树的结点
二叉树的结点
template<class T>
struct BinaryTreeNode
{
BinaryTreeNode<T>* _left;
BinaryTreeNode<T>* _right;
T _data;
BinaryTreeNode(const T& x)
:_left(NULL)
, _right(NULL)
, _data(x)
{}
};
构造二叉树:
在这里invalid就是所谓的非法值,而index就是数组的下标。
比如:int array[10] = { 1, 2, 3, ‘#’, ‘#’, 4, ‘#’, ‘#’, 5, 6 }
这里的invalid就是‘#’.
BinaryTree()
:_root(NULL)
{}
BinaryTree(T* a, size_t n, const T& invalid)
{
size_t index = 0;
_root = CreateTree(a, n, invalid, index);
}
//构建当前树或子树
//这里需要注意的是index要使用&
//若没有使用则index在上一层递归中的++index,对之后的是没有影响的
Node* CreateTree(T* a, size_t n, const T& invalid, size_t &index)
{
Node*root = NULL;
if (index<n&&a[index]!=invalid)
{
root = new Node(a[index]);
root->_left = CreateTree(a, n, invalid, ++index);
root->_right = CreateTree(a,n, invalid, ++index);
}
return root;
}
前序遍历(先根遍历):
(1):先访问根节点;
(2):前序访问左子树;
(3):前序访问右子树; 【1 2 3 4 5 6】
这里需要注意的是下面所有代码使用到递归的我们都会把它分为两个函数
如PrevOrder和_PrevOrder
原因是因为若将_root传入,外部对象是访问不到的。
//前序遍历(递归)
void PrevOrder()
{
_PrevOrder(_root);
cout << endl;
}
void _PrevOrder(Node* root)
{
if (root == NULL)//递归结束的条件
return;
cout << root->_data << " ";
_PrevOrder(root->_left);
_PrevOrder(root->_right);
}
前序遍历非递归:
我们前序遍历的非递归要借助栈的后进先出的特性来完成。
我们将1,2,3分别压栈,3的左为空则停止压栈,取栈顶元素3存放在变量top中,并删除栈顶元素,访问top的右子树,它的右子树也为空,则取栈顶元素2并访问它的右子树,如此重复便是(中序遍历同)
//前序遍历(非递归)
void PrevOrderNonR()
{
stack<Node*> s;
Node* cur = _root;
while (cur ||! s.empty())
{
while (cur)
{
s.push(cur);
cout << cur->_data << " ";
cur = cur->_left;
}
// top从栈取出来表示这个节点的左子树访问过了
// 剩余右子树还没访问,循环子问题访问右树
Node* top = s.top();
s.pop();
//子问题的方式访问右树
cur = top->_right;
}
cout << endl;
}
中序遍历:
(1):中序访问左子树;
(2):访问根节点;
(3):中序访问右子树; 【3 2 4 1 6 5】
//中序遍历(递归)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
中序遍历非递归实现:
这里中序遍历非递归的实现道理与前序遍历非递归的道理相同,如果你掌握了前序遍历