二叉树

在电脑科学中，二元树（Binary tree）是每个节点最多只有两个分支（即不存在分支度大于2的节点）的树结构。通常分支被称作“左子树”或“右子树”。二元树的分支具有左右次序，不能随意颠倒。
二元树的第formula_1层至多拥有formula_2个节点；深度为formula_3的二元树至多总共有formula_4个节点（定义根节点所在深度 formula_5），而总计拥有节点数符合的，称为「满二元树」；深度为formula_3有formula_7个节点的二元树，当且仅当其中的每一节点，都可以和深度formula_3的满二元树，序号1到formula_7的节点一对一对应时，称为完全二元树。对任何一棵非空的二元树formula_10，如果其叶片（终端节点）数为formula_11，分支度为2的节点数为formula_12，则formula_13。
与普通树不同，普通树的节点个数至少为1，而二元树的节点个数可以为0；普通树节点的最大分支度没有限制，而二元树节点的最大分支度为2；普通树的节点无左、右次序之分，而二元树的节点有左、右次序之分。
二元树通常作为资料结构应用，典型用法是对节点定义一个标记函数，将一些值与每个节点相关联。这样标记的二元树就可以实现二元搜寻树和二元堆积，并应用于高效率的搜索和排序。
图论中的定义.
二元树是一个连通的无环图，并且每一个顶点的度不大于3。有根二元树还要满足根节点的度不大于2。有了根节点之后，每个顶点定义了唯一的父节点，和最多2个子节点。然而，没有足够的资讯来区分左节点和右节点。如果不考虑连通性，允许图中有多个连通分量，这样的结构叫做森林。
性质.
二元树是一个有根树，并且每个节点最多有2个子节点。非空的二叉树，若树叶总数为 n0，分支度为2的总数为 n2，则 n0 = n2 + 1。
特殊类型.
满二元树.
在一颗二元树中，除了叶节点外，每个节点都有 2 个子节点。
完美二叉树.
一棵深度为k，且有formula_14个节点的二元树，称为完美二元树（Perfect Binary Tree）。这种树的特点是其中所有内部节点都有两个子节点，并且所有叶子都处于同一级别。
性质.
对于一棵深度为 formula_3 的完美二叉树：
完全二叉树.
在一颗二元树中，若除最后一层外的其余层都是满的，并且最后一层要么是满的，要么在右边缺少连续若干节点，则此二元树为完全二元树（Complete Binary Tree）。具有n个节点的完全二元树的深度为formula_20。深度为k的完全二元树，至少有formula_21个节点，至多有formula_14个节点。
存储.
在程式设计语言中能用多种方法来构造二元树。
顺序存储表示.
二元树可以用阵列或链表来存储，若是满二元树就能紧凑排列而不浪费空间。如果某个节点的索引为i，（假设根节点的索引为0）则在它左子节点的索引会是formula_23，以及右子节点会是formula_24；而它的父节点（如果有）索引则为"formula_25"。这种方法更有利于紧凑存储和更好的访问的局部性，特别是在前序遍历中。然而，它需要连续的存储空间，这样在存储高度为"h"的"n"个节点所组成的一般树时，将浪费很多空间。在最糟糕的情况下，如果深度为h的二元树其每个节点都只有右孩子，则该存储结构需要占用formula_26的空间，实际上却有h个节点，浪费了不少空间，是顺序存储结构的一大缺点。
存储结构.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
基本操作.
<syntaxhighlight lang="cpp" line>
/* 二元树的顺序存储的基本操作(23个)*/
void InitBiTree(SqBiTree T) －－－（SqBiTree & T）
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void CreateBiTree(SqBiTree T)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
int BiTreeDepth(SqBiTree T)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
Status Root(SqBiTree T,TElemType *e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType Value(SqBiTree T,position e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
Status Assign(SqBiTree T,position e,TElemType value)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType Parent(SqBiTree T,TElemType e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType LeftChild(SqBiTree T,TElemType e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType RightChild(SqBiTree T,TElemType e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType LeftSibling(SqBiTree T,TElemType e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
TElemType RightSibling(SqBiTree T,TElemType e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void Move(SqBiTree q,int j,SqBiTree T,int i) /* InsertChild()用到。加 */
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void InsertChild(SqBiTree T,TElemType p,int LR,SqBiTree c)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
typedef int QElemType; /* 设伫列元素类型为整型(序号) */
Status DeleteChild(SqBiTree T,position p,int LR)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void(*VisitFunc)(TElemType); /* 函数变数 */
void PreTraverse(SqBiTree T,int e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void PreOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void InTraverse(SqBiTree T,int e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void InOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void PostTraverse(SqBiTree T,int e)
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void PostOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void LevelOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
</syntaxhighlight>
<syntaxhighlight lang="cpp" line>
void Print(SqBiTree T)
</syntaxhighlight>
二叉链表存储表示.
在使用记录或记忆体位址指标的程式设计语言中，二叉树通常用树结点结构来存储。有时也包含指向唯一的父节点的指标。如果一个结点的子结点个数小于2，一些子结点指针可能为空值，或者为特殊的哨兵结点。 
使用链表能避免顺序储存浪费空间的问题，演算法和结构相对简单，但使用二叉链表，由于缺乏父链的指引，在找回父节点时需要重新扫描树得知父节点的节点位址。
存储结构.
<syntaxhighlight lang="cpp">/* 二叉树的二叉链表存储表示 */
基本操作.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
三叉链表存储表示.
改进于二叉链表，增加父节点的指引，能更好地实现节点间的访问，不过演算法相对复杂。
当二叉树用三叉链表表示时，有N个结点，就会有N+2个空指针。
存储结构.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
基本操作.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
遍历.
我们经常希望访问树中的每一个结点并且查看它的值。有很多常见的顺序来访问所有的结点，而且每一种都有有用的性质。
深度优先遍历.
在深度优先顺序中，我们希望从根结点访问最远的结点。和图的深度优先搜索不同的是，不需记住访问过的每一个结点，因为树中不会有环。前序，中序和后序遍历都是深度优先遍历的特例。
前（先）序、中序、后序遍历.
遍历二叉树：L、D、R分别表示遍历左子树、访问根结点和遍历右子树，则先(根)序遍历二叉树的顺序是DLR，中(根)序遍历二叉树的顺序是LDR，后(根)序遍历二叉树的顺序是LRD。还有按层遍历二叉树。这些方法的时间复杂度都是O(n)，n为结点个数。
如果T2是由有序树T转换而来的二叉树，那么T中结点的前序就是T2中结点的前序，T中结点的后序就是T2中结点的中序。任何一棵二叉树的叶结点在先序、中序和后序遍历中的相对次序不发改变。设n,m为一棵二叉树上的两个结点，在中序遍历时，n在m前的条件是n在m的左方。前序序列和中序序列相同的二叉树为空树或任一结点均无左孩子的非空二叉树；中序序列和后序序列相同的二叉树为空树或任一结点均无右孩子的非空二叉树；前序序列和后序序列相同的二叉树为空树或仅有一个结点的二叉树。
假设我们有一个包含值的codice_1和指向两个子结点的codice_2和codice_3的树结点结构。我们可以写出这样的过程：
这样会用前序打印出树中的值。在前序，每个结点在访问它的子结点之前访问。类似地，如果打印语句在最后，每个结点在访问他的子节点之后访问，树中的值会用后序来打印。在这两种情况中，左子树中的值比右子树中得值先打印。
最后，上面的中序遍历，每个结点在访问左子树和右子树之间访问。这在遍历二叉搜寻树时很常用，因为它能用递增的顺序来遍历所有的值。
为什么呢？如果"n"是二叉搜寻树的结点，那么"n"的左子树的所有结点的值都比n的值要小，而且"n"的右子树的所有节点的值都比n的值要大。因此，如果我们顺序遍历左子树，然后访问"n"，然后顺序遍历右子树。我们就已经循序存取了整个树。
后序遍历伪代码如下：
以上的递回演算法使用与树的高度成比例的栈空间。如果我们在每个结点中存储指向父结点的指标，那样可以使用反复运算演算法，只使用常数空间实现所有这些遍历。然而，指向父结点的指标占用更多的空间。这只在需要指向父节点的指标或栈空间有限时才使用。例如，
这是一个中序遍历的反复运算演算法： 
广度优先遍历.
和深度优先遍历不同，广度优先遍历会先访问离根节点最近的节点。二叉树的广度优先遍历又称按层次遍历。演算法借助伫列实现。
转换自多叉树.
一般有序树可映射为二叉树，但反之未必成立。
n叉树转换为二叉树的方法：二叉树中结点x的左子结点为n叉树中结点x的左子结点；二叉树中结点x的右子结点为n叉树中结点x的第一个右边的同级结点y。
二叉树当且仅当根节点没有右子结点时可转换为n叉树。
N in the ordered tree corresponds to a node N' in the binary tree; the "left" child of N' is the node corresponding to the first child of N, and the "right" child of N' is the node corresponding to the N's next sibling --- that is, the next node in order among the children of the parent of N
One way of thinking about this is that each node's children are in a linked list, chained together with their "right" fields, and the node only has a pointer to the beginning or head of this list, through its "left" field.
例如，在左边的树中，A有6个子结点{B,C,D,E,F,G}。它能被转换成右边的二叉树。
树的二叉链表标记法（孩子兄弟标记法）.
树的二叉链表标记法（孩子兄弟标记法）是树和二叉树转换的媒介。
存储结构.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
基本操作.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
 return a;
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
 if(Value(p)==cur_e) /* 下一个兄弟为cur_e */
 return Value(t); /* 返回双亲 */
 EnQueue(&q,p); /* 入队下一个兄弟 */
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
线索二叉树.
线索二叉树（threaded binary tree，保留遍历时结点在任一序列的前驱和后继的资讯）：若结点有左子树，则其lchild域指示其左孩子，否则令lchild域指示其前驱；若结点有右子树，则其rchild域指示其右孩子，否则令rchild指示其后继。还需在结点结构中增加两个标志域LTag和RTag。LTag=0时，lchild域指示结点的左孩子，LTag=1时，lchild域指示结点的前驱；RTag=0时，rchild域指示结点的右孩子，RTag=1时，rchild域指示结点的后继。以这种结点结构构成的二叉链表作为二叉树的存储结构，叫做线索链表，其中指向结点前驱和后继的指标叫做线索，加上线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。若对二叉树进行中序遍历，则所得的线索二叉树称为中序线索二叉树，线索链表称为为中序线索链表。线索二叉树是一种物理结构。
在中序线索树找结点后继的规律是：若其右标志为1，则右链为线索，指示其后继，否则遍历其右子树时访问的第一个结点（右子树最左下的结点）为其后继；找结点前驱的规律是：若其左标志为1，则左链为线索，指示其前驱，否则遍历左子树时最后访问的一个结点（左子树中最右下的结点）为其前驱。
在后序线索树中找到结点的后继分三种情况：
二叉线索存储表示.
存储结构.
二叉树的二叉线索存储表示：在线索链表上添加一个头结点，并令其lchild域的指标指向二叉树的根结点，其rchild域的指标指向中序遍历时访问的最后一个结点。令二叉树中序序列中的第一个结点的lchild域指标和最后一个结点的rchild域的指标均指向头结点，这样就建立了一个双向线索链表。二叉树常采用二叉链表方式存储。
<syntaxhighlight lang="cpp">
</syntaxhighlight>
基本操作.
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>
<syntaxhighlight lang="cpp">
</syntaxhighlight>