二叉树相关概念
二叉树:树中每个节点至多有两个子节点,称为左子节点和右子节点;如果用递归的方式来定义二叉树,我们可以说:”一个二叉树若不为空,便是由一个根节点和左右子树构成”。下图所示即为一颗二叉树:
满二叉树:树中除了叶子节点,每个节点都有两个子节点
完全二叉树:在满足满二叉树的性质后,最后一层的叶子节点均集中在最左边,即完全二叉树中若一个节点有子节点,则它必有左节点,不一定有右节点。上图所示的二叉树即为一个完全二叉树
二叉树的实现
数据结构
二叉树每个节点都包含数据域和指针域(左指针和右指针)
template <class T>
struct BinaryTreeNode
{
T _data;
BinaryTreeNode<T>* _left;
BinaryTreeNode<T>* _right;
BinaryTreeNode(const T& x)
:_data(x)
, _left(NULL)
, _right(NULL)
{}
};
二叉树实现
二叉树的遍历方式(以上图所示二叉树为例):
- 先序遍历:根-左-右;1-2-3-4-5-6
- 中序遍历:左-右-根;3-2-4-1-6-5
- 后序遍历:左-根-右;3-4-2-6-5-1
- 层序遍历:从根节点开始,每一层按照从左至右的方式进行遍历;1-2-5-3-4-6
类的成员函数
构造函数
BinaryTree()
:_root(NULL)
{}
创建二叉树,每一颗二叉树都是有它的左子树和它的右子树构成的,根据这个思想,我们可以通过递归的方式来创建二叉树:
BinaryTree(T* arr, size_t n,const T& invalid)
{
size_t index = 0;
_root = _CreateTree(arr, n, invalid, index);
}
Node* _CreateTree(const T* arr, size_t n, const T& invalid, size_t& index)
{
//创建左树,创建右树
Node* root = NULL;
if (index < n && arr[index] != invalid)
{
root = new Node(arr[index]);
root->_left = _CreateTree(arr, n, invalid, ++index);
root->_right = _CreateTree(arr, n, invalid, ++index);
}
return root;
}
拷贝构造函数
BinaryTree(const BinaryTree<T>& tree)
{
_root = _Copy(tree._root);
}
Node* _Copy(Node* root)
{
if (root == NULL)
return NULL;
Node* newRoot = new Node(root->_data);
newRoot->_left = _Copy(root->_left);
newRoot->_right = _Copy(root->_right);
return newRoot;
}
赋值运算符重载
//赋值运算符重载(传统写法)
BinaryTree<T>& operator =(const BinaryTree<T> tree)
{
if (this != &tree)
{
_Destroy(_root);
_root = _Copy(tree._root);
}
return *this;
//赋值运算符重载(现代写法)
BinaryTree<T>& operator=(BinaryTree<T>& tree)
{
if (this != &tree)
{
swap(_root, tree._root);
}
return *this;
}
析构函数
~BinaryTree()
{
_Destroy(_root);
_root = NULL;
}
void _Destroy(Node*& root)
{
if (root == NULL)
return;
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}
二叉树的遍历
先序遍历(递归)
void PrevOrder()
{
_PrevOrder(_root);
}
void _PrevOrder(Node* root)
{
if (root == NULL)
return;
cout << root->_data << " ";
_PrevOrder(root->_left);
_PrevOrder(root->_right);
}
先序遍历(非递归):先序遍历采用的访问方式是:根-左-右。将根和左节点依次压入栈中,直到最左节点入栈,每一个左节点都可作为一个根节点进行访问;将栈中元素依次出栈,每出栈一个,则访问该节点的右子树,直到栈为空;这样我们就可以按照先序遍历的方法访问二叉树的每个节点了
void PrevOrderNonR()
{
//将左树依次压入栈中,然后从栈顶依次访问每一个左树的右子树
stack<Node*> s;
Node* cur = _root;
while (!s.empty() || cur)
{
while (cur)
{
s.push(cur);
cout << cur->_data << " ";
cur = cur->_left;
}
//左子树入栈完毕
Node* top = s.top();
s.pop();
cur = top->_right;
}
cout << endl;
}
中序遍历(递归)
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
中序遍历(非递归):中序遍历顺序为:左-根-右。同样的借助栈数据结构,将根和左节点依次入栈,直到最左节点进栈;访问栈顶元素,将其出栈,访问其右子树,直到栈空为止
void InOrderNonR()
{
stack<Node*> s;
Node* cur = _root;
while (!s.empty() || cur)
{
while (cur)
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
cout << top->_data << " ";
s.pop();
cur = top->_right;
}
cout << endl;
}
后序遍历(递归)
void PostOrder()
{
_PostOrder(_root);
cout << endl;
}
void _PostOrder(Node* root)
{
if (root == NULL)
return;
_PostOrder(root->_left);
_PostOrder(root->_right);
cout << root->_data << " ";
}
后序遍历(非递归):同样借助栈数据结构,将根和左节点依次入栈,每次在栈顶元素出栈前将栈顶元素进行记录,方便我们访问它的右节点
void PostOrderNonR()
{
stack<Node*> s;
Node* cur = _root;
Node* prev = NULL;
while (!s.empty() || cur)
{
while (cur)
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
if (top->_right == NULL || top->_right == prev)
{
cout << top->_data << " ";
prev = top;
s.pop();
}
else
{
cur = top->_right;
}
}
cout << endl;
}
层序遍历:因为层序遍历采用的方式是每一层从左向右进行访问,所以我们借助队列数据结构先进先出的特点,将每一层元素依次入队,在依次出队进行访问
void LevelOrder()
{
queue<Node*> q;
if (_root)
q.push(_root);
while (!q.empty())
{
Node* front = q.front();
q.pop();
cout << front->_data << " ";
if (front->_left)
q.push(front->_left);
if (front->_right)
q.push(front->_right);
}
cout << endl;
}
二叉树节点相关计算
二叉树节点个数
size_t Size()
{
return _Size(_root);
}
size_t _Size(Node* root)
{
if (root == NULL)
return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
二叉树叶子节点个数
size_t LeafSize()
{
return _LeafSize(_root);
}
size_t _LeafSize(Node* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
return _LeafSize(root->_left) + _LeafSize(root->_right);
}
二叉树第K层节点个数
size_t KLevelSize(size_t k)
{
assert(k > 0);
return _KLevelSize(_root, k);
}
size_t _KLevelSize(Node* root, const T& k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
return _KLevelSize(root->_left, k - 1) + _KLevelSize(root->_right, k - 1);
}
二叉树深度
size_t Depth()
{
return _Depth(_root);
}
size_t _Depth(Node* root)
{
if (root == NULL)
return 0;
if (root->_left == NULL&&root->_right == NULL)
return 1;
size_t leftDepth = _Depth(root->_left);
size_t rightDepth = _Depth(root->_right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
二叉树的其他重要操作
查找某个节点
Node* Find(const T& x)
{
return _Find(_root, x);
}
Node* _Find(Node* root, const T& x)
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
Node* left = _Find(root->_left, x);
if (left->_data == x)
return left;
Node* right = _Find(root->_right, x);
if (right->_data == x)
return right;
return NULL;
}
判断一棵树是否是完全二叉树:完全二叉树的n-1层都是满的,第n层的节点都集中在最左边;所以判断一颗二叉树是否是完全二叉树我们可以分下面几种情况考虑:
- 首先我们按照层序遍历的方式,若一个节点左右孩子都不为空,则该元素出队,将其左右孩子入队
- 若一个节点的左孩子为空,有孩子不为空,则该树一定不是完全二叉树
- 若一个节点的左孩子不为空,右孩子为空;或者其左右孩子都为空,可以进行进一步的判断,此时我们只需判断最后一层的节点是否都是叶子节点,即最后一层节点的左右孩子是否为空,若为空,则该树是完全二叉树,否则不是
bool IsCompleteBinaryTree()
{
queue<Node*> q;
if (_root == NULL)
return false;
q.push(_root);
while (!q.empty())
{
Node* front = q.front();
if (front->_left && front->_right)
{
q.pop();
q.push(front->_left);
q.push(front->_right);
}
if (front->_left == NULL && front->_right != NULL)
return false;
if ((front->_left != NULL &&front->_right == NULL)
|| (front->_left == NULL && front->_right == NULL))
{
q.pop();
while (!q.empty())
{
front = q.front();
if (front->_left == NULL && front->_right == NULL)
{
q.pop();
}
else
{
return false;
}
}
return true;
}
}
return true;
}
关于二叉树的实现完整代码,大家可以访问我的github进行下载查看:
https://github.com/l-cong/code