编程基础 - 二叉树 (Binary Tree)
返回分类:全部文章 >> 基础知识
本文将介绍二叉树的基础知识,并用C++实现主要方法。
在查看本文之前,需要一些数据结构和程序语言的基础,要对“树 (tree) ”的概念有一些了解。
其中的方法还需要熟悉“栈(stack) ”、“队列(queue) ”和“递归”。
1 二叉树简述 (Introduction)
二叉树:
二叉树逻辑上有五种基本形态:
(1)空
(2)只有一个根结点
(3)只有左子树
(4)只有右子树
(5)完全二叉树
一些常见二叉树:
(1)完全二叉树 (Complete Binary Tree) :从深度上看,最深层有叶节点,且叶节点从左到右依次排序,其它层结点达到最大值;
(2)满二叉树 (Full Binary Tree) :
注意:国内与国际定义不同,看外文文献或是看外文算法时要注意区分。
(3)二叉排序树 (Binary Search Tree) :也称二叉查找树和二叉搜索树。空树或左子二叉排序树的结点都小于根结点且右子二叉排序树的结点都大于根结点的二叉树;
(4)平衡二叉树 (Balanced Binary Tree) :又称AVL树。空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树;
(5)线索二叉树 (Threaded Binary Tree) :加上线索指针(存放指向结点在某种遍历次序下的前驱和后继结点的指针)的二叉树;
(6)哈夫曼树 (Huffman Tree) :带权路径长度达到最小的二叉树,也称最优二叉树。
二叉树与树的不同点:
(1)结点最大度数为2(树无限制)
(2)子树分左右(树不分)
2 二叉树性质 (Properties)
(提示:在CSDN的app中,公式有可能会出现乱码或格式错乱,如果出现请打开网页)
(1)在非空二叉树中,且层次从1开始,则二叉树第 i i i 层最多有 2 i 1 ( i ≥ 1 ) 2^{i-1} (i \ge 1) 2 i 1 ( i ≥ 1 ) 个结点;
(2)高度为 h h h 的二叉树最多有 2 h 1 ( h ≥ 1 ) 2^h-1 (h \ge 1) 2 h 1 ( h ≥ 1 ) 个结点
(3)对任意一棵非空二叉树,如果其叶结点有 n 0 n_0 n 0 个,度为2的非叶结点有 n 2 n_2 n 2 个,则有 n 0 = n 2 + 1 n_0 = n_2 + 1 n 0 = n 2 + 1 ;
(4)具有 n ( n ≥ 0 ) n (n \ge 0) n ( n ≥ 0 ) 个结点的完全二叉树的高度为 log 2 ( n + 1 ) \lceil \log_2 (n+1) \rceil log 2 ( n + 1 ) ;
注意:另一个公式 log 2 n + 1 \lfloor \log_2 n \rfloor + 1 log 2 n + 1 对 n = 0 n = 0 n = 0 不适用。
(5)有 n n n 个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系( i i i 为结点标号):
若 i = 0 i = 0 i = 0 ,则 i i i 无双亲;
若 i > 0 i > 0 i > 0 ,则 i i i 双亲为 ( i 1 ) ÷ 2 \lfloor (i-1) \div 2 \rfloor ( i 1 ) ÷ 2 ;
若 2 × i + 1 < n 2 \times i + 1 < n 2 × i + 1 < n ,则 i i i 的左子女为 2 × i + 1 2 \times i + 1 2 × i + 1 ;
若 2 × i + 2 < n 2 \times i + 2 < n 2 × i + 2 < n ,则 i i i 的左子女为 2 × i + 2 2 \times i + 2 2 × i + 2 ;
若 i i i 为偶数,且 i ≠ 0 i \ne 0 i = 0 ,则左兄弟为 i 1 i-1 i 1 ;
若 i i i 为奇数,且 i ≠ n 1 i \ne n-1 i = n 1 ,则右兄弟为 i + 1 i+1 i + 1 ;
(6)给定 n n n 个节点,能构成 1 n + 1 C 2 n n \frac{1}{n+1} C_{2n}^{n} n + 1 1 C 2 n n (卡特兰数)种不同的二叉树;
(7)设有 i i i 个枝点, I I I 为所有枝点的道路长度总和,则叶的道路长度总和 J = I + 2 × i J = I + 2 \times i J = I + 2 × i
3 二叉树的结构 (Structure)
而在二叉树的结构中,依然可以分成两种:
顺序存储
顺序存储一般是存储在数组或列表中。
(性质(5)中是从0存储,但实际下标常常从1开始,0留空或作他用)
即:
tree = [ NULL, a, b, c, d, e, NULL, NULL, NULL, NULL, f, g, ... ]
index = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... ]
从1开始存储,假设 P i P_i P i 为结点下标, L i L_i L i 为其左孩子下标, R i R_i R i 为其右孩子下标。
由结点下标求左右孩子下标:
L i = P i × 2 R i = P i × 2 + 1
\begin{aligned}
L_i &= P_i \times 2 \\
R_i &= P_i \times 2 + 1 \\
\end{aligned}
L i R i = P i × 2 = P i × 2 + 1
由左右孩子下标求结点下标:
P i = L i ÷ 2 P i = R i ÷ 2
\begin{aligned}
P_i &= L_i \div 2 \\
P_i &= \lfloor R_i \div 2 \rfloor
\end{aligned}
P i P i = L i ÷ 2 = R i ÷ 2
链式存储
二叉树结点一般定义为:
template < typename T>
class BinaryTreeNode
{
T element;
BinaryTreeNode< T> * leftChild;
BinaryTreeNode< T> * rightChild;
BinaryTreeNode ( const T& x) : element ( x) , leftChild ( 0 ) , rightChild ( 0 ) { }
} ;
4 二叉树的遍历 (Traversal)
二叉树的基本遍历分为四种(V=Visit,L=Left,R=Right):
以下我们用链式结构来说明如何遍历(数组结构同理,只是改成了下标的运算)。
4.1 中序遍历 (Inorder)
步骤(二叉树不为空):
(1)遍历左子树(Left);
(2)访问当前结点(Visit);
(3)遍历右子树(Right)。
递归方法代码一般为:
template < typename T>
void InorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node != 0 )
{
InorderTraversal ( node- > leftChild) ;
( * pVisit) ( * node) ;
InorderTraversal ( node- > rightChild) ;
}
}
例如,abcdefg结点:
a
/ \
b c
/ \
d e
/ \
f g
中序遍历步骤为:
(1)a,遍历左子树,即:a->b
(2)b,遍历左子树,即:b->d
(3)d,没有左子树
(4)d,访问当前结点,即:Visit(d)
(5)d,没有右子树
(6)d,返回,即:b<-d
(7)b,访问当前结点,即:Visit(b)
(8)b,遍历右子树,即:b->e
(9)e,遍历左子树,即:e->f
(10)f,没有左子树
(11)f,访问当前结点,即:Visit(f)
(12)f,没有右子树
(13)f,返回,即:e<-f
(14)e,访问当前结点,即:Visit(e)
(15)e,遍历右子树,即:e->g
(16)g,没有左子树
(17)g,访问当前结点,即:Visit(g)
(18)g,没有右子树
(19)g,返回,即:e<-g
(20)e,返回,即:b<-e
(21)b,返回,即:a<-b
(22)a,访问当前结点,即:Visit(a)
(23)a,遍历右子树,即:a->c
(24)c,没有左子树
(25)c,访问当前结点,即:Visit(c)
(26)c,没有右子树
(27)c,返回,即:a<-c
(28)a,返回,即:NULL <-a
中序遍历结果为:dbfegac
4.2 前序遍历 (Preorder)
步骤(二叉树不为空):
(1)访问当前结点(Visit);
(2)遍历左子树(Left);
(3)遍历右子树(Right)。
递归方法代码一般为:
template < typename T>
void PreorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node != 0 )
{
( * pVisit) ( * node) ;
PreorderTraversal ( node- > leftChild) ;
PreorderTraversal ( node- > rightChild;
}
}
例如,abcdefg结点:
a
/ \
b c
/ \
d e
/ \
f g
前序遍历步骤为:
(1)a,访问当前结点,即:Visit(a)
(2)a,遍历左子树,即:a->b
(3)b,访问当前结点,即:Visit(b)
(4)b,遍历左子树,即:b->d
(5)d,访问当前结点,即:Visit(d)
(6)d,没有左子树
(7)d,没有右子树
(8)d,返回,即:b<-d
(9)b,遍历右子树,即:b->e
(10)e,访问当前结点,即:Visit(e)
(11)e,遍历左子树,即:e->f
(12)f,访问当前结点,即:Visit(f)
(13)f,没有左子树
(14)f,没有右子树
(15)f,返回,即:e<-f
(16)e,遍历右子树,即:e->g
(17)g,访问当前结点,即:Visit(g)
(18)g,没有左子树
(19)g,没有右子树
(20)g,返回,即:e<-g
(21)e,返回,即:b<-e
(22)b,返回,即:a<-b
(23)a,遍历右子树,即:a->c
(24)c,访问当前结点,即:Visit(c)
(25)c,没有左子树
(26)c,没有右子树
(27)c,返回,即:a<-c
(28)a,返回,即:NULL <-a
前序遍历结果为:abdefgc
4.3 后序遍历 (Postorder)
步骤(二叉树不为空):
(1)遍历左子树(Left);
(2)遍历右子树(Right)。
(3)访问当前结点(Visit);
递归方法代码一般为:
template < typename T>
void PostorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node != 0 )
{
PostorderTraversal ( node- > leftChild) ;
PostorderTraversal ( node- > rightChild;
( * pVisit) ( * node) ;
}
}
例如,abcdefg结点:
a
/ \
b c
/ \
d e
/ \
f g
后序遍历步骤为:
(1)a,遍历左子树,即:a->b
(2)b,遍历左子树,即:b->d
(3)d,没有左子树
(4)d,没有右子树
(5)d,访问当前结点,即:Visit(d)
(6)d,返回,即:b<-d
(7)b,遍历右子树,即:b->e
(8)e,遍历左子树,即:e->f
(9)f,没有左子树
(10)f,没有右子树
(11)f,访问当前结点,即:Visit(f)
(12)f,返回,即:e<-f
(13)e,遍历右子树,即:e->g
(14)g,没有左子树
(15)g,没有右子树
(16)g,访问当前结点,即:Visit(g)
(17)g,返回,即:e<-g
(18)e,访问当前结点,即:Visit(e)
(19)e,返回,即:b<-e
(20)b,访问当前结点,即:Visit(b)
(21)b,返回,即:a<-b
(22)a,遍历右子树,即:a->c
(23)c,没有左子树
(24)c,没有右子树
(25)c,访问当前结点,即:Visit(c)
(26)c,返回,即:a<-c
(27)a,访问当前结点,即:Visit(a)
(28)a,返回,即:NULL <-a
后序遍历结果为:dfgebca
4.4 已知前(或后)中序求二叉树 (Order Infer Tree)
前序(或后序)与中序求二叉树推导方法:
已知:
前序遍历结果为:ABDEFCG
中序遍历结果为:DBFEACG
后序遍历结果为:DFEBGCA
推导过程:
在前序ABDEFGC中,A为根结点,则在中序DBFEACG中找到A,并分成左右子树:
在前序BDEF中,B为根结点,则在中序DBFE中找到B,并分成左右子树:
在前序EF中,E为根结点,则在中序FE中找到E,并分成左右子树:
在前序CG中,C为根结点,则在中序CG中找到C,并分成左右子树:
同理可以用后序和中序推导二叉树。
比较重要的结论:
5 一些常用方法的代码 (Code of Some Usual Methods)
5.1 结点数量 (Node Count)
递归
template < typename T>
int Count ( BinaryTreeNode< T> * root)
{
if ( root == 0 )
{
return 0 ;
}
return Count ( root- > leftChild) + Count ( root- > rightChild) + 1 ;
}
非递归
template < typename T>
int Count ( BinaryTreeNode< T> * root)
{
if ( root == 0 )
{
return 0 ;
}
int count = 0 ;
std:: queue< BinaryTreeNode< T> * > nodeQueue;
nodeQueue. push ( root) ;
BinaryTreeNode< T> * node;
while ( ! nodeQueue. empty ( ) )
{
count++ ;
node = nodeQueue. front ( ) ;
nodeQueue. pop ( ) ;
if ( node- > leftChild != 0 )
{
nodeQueue. push ( node- > leftChild) ;
}
if ( node- > rightChild != 0 )
{
nodeQueue. push ( node- > rightChild) ;
}
}
return count;
}
5.2 深度或高度 (Depth or Height)
递归
template < typename T>
int DepthOrHeight ( BinaryTreeNode< T> * root)
{
if ( root == 0 )
{
return 0 ;
}
int left = DepthOrHeight ( root- > leftChild) ;
int right = DepthOrHeight ( root- > rightChild) ;
return ( left > right ? left : right) + 1 ;
}
非递归
template < typename T>
int DepthOrHeight ( BinaryTreeNode< T> * root)
{
if ( root == 0 )
{
return 0 ;
}
int depth = 0 ;
std:: queue< BinaryTreeNode< T> * > * lineNodes = new std:: queue< BinaryTreeNode< T> * > ( ) ;
std:: queue< BinaryTreeNode< T> * > * nextNodes = new std:: queue< BinaryTreeNode< T> * > ( ) ;
std:: queue< BinaryTreeNode< T> * > * tmp = 0 ;
lineNodes- > push ( root) ;
BinaryTreeNode< T> * node;
while ( ! lineNodes- > empty ( ) )
{
depth++ ;
while ( ! lineNodes- > empty ( ) )
{
node = lineNodes- > front ( ) ;
lineNodes- > pop ( ) ;
if ( node- > leftChild != 0 )
{
nextNodes- > push ( node- > leftChild) ;
}
if ( node- > rightChild != 0 )
{
nextNodes- > push ( node- > rightChild) ;
}
}
tmp = lineNodes;
lineNodes = nextNodes;
nextNodes = tmp;
tmp = 0 ;
}
delete lineNodes;
delete nextNodes;
return depth;
}
5.3 创建完全二叉树 (Create Complete Binary Tree)
BinaryTreeNode< int > * CreateIntergerCompleteBinaryTree ( const int rootVal,
const int nodeCount,
const int elementInterval)
{
if ( nodeCount < 1 )
{
throw std:: invalid_argument ( "`nodeCount` is less than one." ) ;
}
BinaryTreeNode< int > * btTree = new BinaryTreeNode< int > * ( rootVal)
if ( nodeCount == 1 )
{
return btTree;
}
std:: queue< BinaryTreeNode< int > * > nodeQueue;
BinaryTreeNode< int > * node = btTree;
nodeQueue. push ( node) ;
int val = rootVal;
for ( int i = 1 ; i < nodeCount; )
{
node = nodeQueue. front ( ) ;
nodeQueue. pop ( ) ;
val + = elementInterval;
node- > leftChild = new BinaryTreeNode< int > * ( val) ;
nodeQueue. push ( node- > leftChild) ;
i++ ;
if ( i < nodeCount)
{
val + = elementInterval;
node- > rightChild = new BinaryTreeNode< int > * ( val) ;
nodeQueue. push ( node- > rightChild) ;
i++ ;
}
}
nodeQueue. clear ( ) ;
return btTree;
}
5.4 创建满二叉树 (Create Full Binary Tree)
BinaryTreeNode< int > * CreateIntergerFullBinaryTree ( const int rootVal,
const int depth,
const int elementInterval)
{
if ( depth < 1 )
{
throw std:: invalid_argument ( "`depth` is less than one." ) ;
}
int nodeCount = std:: exp2 ( depth) - 1 ;
return CreateIntergerCompleteBinaryTree ( rootVal, nodeCount, elementInterval) ;
}
5.5 层序遍历 (Layer Traversal)
template < typename T>
void LayerTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node == 0 )
{
return ;
}
std:: queue< BinaryTreeNode< T> * > nodeQueue;
nodeQueue. push ( node) ;
BinaryTreeNode< T> * loopNode;
while ( ! nodeQueue. empty ( ) )
{
loopNode = nodeQueue. front ( ) ;
nodeQueue. pop ( ) ;
( * pVisit) ( * loopNode) ;
if ( loopNode- > leftChild != 0 )
{
nodeQueue. push ( loopNode- > leftChild) ;
}
if ( loopNode- > rightChild != 0 )
{
nodeQueue. push ( loopNode- > rightChild) ;
}
}
}
5.6 非递归中序遍历 (Non-recursive Inorder Traversal)
template < typename T>
void InorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node == 0 )
{
return ;
}
std:: stack< BinaryTreeNode< T> * > nodeStack;
BinaryTreeNode< T> * loopNode = node;
do
{
while ( loopNode != 0 )
{
nodeStack. push ( loopNode) ;
loopNode = loopNode- > leftChild;
}
if ( ! nodeStack. empty ( ) )
{
loopNode = nodeStack. top ( ) ;
nodeStack. pop ( ) ;
( * pVisit) ( * loopNode) ;
loopNode = loopNode- > rightChild;
}
} while ( loopNode != 0 || ! nodeStack. empty ( ) ) ;
}
5.7 非递归前序遍历 (Non-recursive Preorder Traversal)
template < typename T>
void PreorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node == 0 )
{
return ;
}
std:: stack< BinaryTreeNode< T> * > nodeStack;
BinaryTreeNode< T> * loopNode = node;
nodeStack. push ( loopNode) ;
while ( ! nodeStack. empty ( ) )
{
loopNode = nodeStack. top ( ) ;
nodeStack. pop ( ) ;
( * pVisit) ( * loopNode) ;
if ( loopNode- > rightChild != 0 )
{
nodeStack. push ( loopNode- > rightChild) ;
}
if ( loopNode- > leftChild != 0 )
{
nodeStack. push ( loopNode- > leftChild) ;
}
}
}
5.8 非递归后序遍历 (Non-recursive Postorder Traversal)
template < typename T>
void PostorderTraversal ( BinaryTreeNode< T> * node, void ( * pVisit) ( BinaryTreeNode< T> & ) )
{
if ( node == 0 )
{
return ;
}
std:: stack< BinaryTreeNode< T> * > nodeStack;
std:: stack< BinaryTreeNode< T> * > rightStack;
bool right = false ;
BinaryTreeNode< T> * loopNode = node;
do
{
if ( ! right)
{
while ( loopNode != 0 )
{
nodeStack. push ( loopNode) ;
loopNode = loopNode- > leftChild;
}
}
if ( ! nodeStack. empty ( ) )
{
loopNode = nodeStack. top ( ) ;
if ( loopNode- > rightChild == 0 )
{
nodeStack. pop ( ) ;
( * pVisit) ( * loopNode) ;
right = true ;
}
else if ( ! rightStack. empty ( ) && loopNode == rightStack. top ( ) )
{
nodeStack. pop ( ) ;
rightStack. pop ( ) ;
( * pVisit) ( * loopNode) ;
}
else
{
rightStack. push ( loopNode) ;
loopNode = loopNode- > rightChild;
right = false ;
}
}
} while ( ! nodeStack. empty ( ) ) ;
}
6 主函数与测试 (Main Method and Testing)
二叉树代码较多,以上已给出主要代码,其它代码省略;
二叉排序树(二叉搜索树,二叉查找树)在以后说明;
以下只是主函数。
6.1 主函数 (Main Method)
#include <typeinfo>
#include <string>
#include <vector>
#include <unordered_map>
#include "binary_tree.h"
#include "binary_search_tree.h"
#include "binary_tree_factory.h"
#include <iomanip>
#include <iostream>
using namespace std;
using namespace BinaryTrees;
unordered_map< int , string> enum2str =
{
{ 0 , "层序" } ,
{ 1 , "前序" } ,
{ 2 , "后序" } ,
{ 3 , "中序" } ,
{ 4 , "中序镜像" } ,
{ 5 , "前序镜像" } ,
{ 6 , "后序镜像" }
} ;
void PrintBinaryTree ( BinaryTree< int > & tree)
{
for ( int type = BTTraversalType:: Normal; type <= BTTraversalType:: PostorderMirror; type++ )
{
cout << setiosflags ( ios:: left) << setw ( 12 ) << setfill ( ' ' ) << enum2str[ type] << ":" ;
for ( BinaryTree< int > :: Iterator it = tree. begin ( ( BTTraversalType) type) ; it != tree. end ( ) ; it++ )
{
cout << ( char ) ( ( * it) . GetElement ( ) ) << " " ;
}
cout << endl;
}
cout << "元素数量:" << tree. GetCount ( ) << endl;
cout << "树的深度:" << tree. GetDepth ( ) << endl;
cout << endl;
}
int main ( )
{
int rootVal = 'a' ;
int nodeCount = 5 ;
int interval = 1 ;
int depth = 3 ;
BinaryTree< int > * cbtTree = BinaryTreeFactory:: CreateIntergerCompleteBinaryTree ( rootVal, nodeCount, interval) ;
cout << "完全二叉树[a, e]的遍历:" << endl;
PrintBinaryTree ( * cbtTree) ;
BinaryTree< int > fbtTree;
BinaryTreeFactory:: InitializeIntergerFullBinaryTree ( rootVal + nodeCount, depth, interval, fbtTree) ;
cout << "满二叉树[f, l]的遍历:" << endl;
PrintBinaryTree ( fbtTree) ;
BinaryTree< int > addTree = BinaryTree< int > ( 'z' , cbtTree, & fbtTree) ;
cout << "合并二叉树的遍历:" << endl;
PrintBinaryTree ( addTree) ;
vector< int > vals = { 'a' , 'h' , 'B' , 'Y' , 'z' , 'g' , 'T' , 'b' , 'X' } ;
BinarySearchTree* bsTree = new BinarySearchTree ( vals) ;
bsTree- > AddNode ( 'U' ) ;
cout << "二叉排序树的遍历,加入树顺序:a h B Y z g T b X U" << endl;
cout << "(注:大写字母ASCII小于小写字母)" << endl;
PrintBinaryTree ( * bsTree) ;
delete cbtTree;
delete bsTree;
return 0 ;
}
6.2 打印结果 (Print Output)
完全二叉树[a, e]的遍历:
层序 :a b c d e
前序 :a b d e c
后序 :d e b c a
中序 :d b e a c
中序镜像 :c a e b d
前序镜像 :a c b e d
后序镜像 :c e d b a
元素数量:5
树的深度:3
满二叉树[f, l]的遍历:
层序 :f g h i j k l
前序 :f g i j h k l
后序 :i j g k l h f
中序 :i g j f k h l
中序镜像 :l h k f j g i
前序镜像 :f h l k g j i
后序镜像 :l k h j i g f
元素数量:7
树的深度:3
合并二叉树的遍历:
层序 :z a f b c g h d e i j k l
前序 :z a b d e c f g i j h k l
后序 :d e b c a i j g k l h f z
中序 :d b e a c z i g j f k h l
中序镜像 :l h k f j g i z c a e b d
前序镜像 :z f h l k g j i a c b e d
后序镜像 :l k h j i g f c e d b a z
元素数量:13
树的深度:4
二叉排序树的遍历,加入树顺序:a h B Y z g T b X U
(注:大写字母ASCII小于小写字母)
层序 :a B h Y g z T b X U
前序 :a B Y T X U h g b z
后序 :U X T Y B b g z h a
中序 :B T U X Y a b g h z
中序镜像 :z h g b a Y X U T B
前序镜像 :a h z g b B Y T X U
后序镜像 :z b g h U X T Y B a
元素数量:10
树的深度:6
请按任意键继续. . .