定义及主要特性
递归定义
二叉树由结点的有限集合组成,这个组合或者为空,或者由一个根结点及两棵不相交的,分别称作这个根的左子树和右子树的二叉树组成。
特点
1.每个结点至多有两棵子树;
2.二叉树的子树有左、右之分,且其次序不能任意颠倒
基本形态
相关术语
从一个结点到它的两个子结点都有边(edge)相连,此结点称为它的子结点的父结点(parent);
如果一棵树的一串结点n1,n2,…,nk有如下关系:
结点ni是ni+1的父结点(1 <= i < k),就把n1,n2,…,nk称为一条由n1至nk的路径(path);
这条路径的长度(length)是k-1(因为k个结点是用k-1条边连接起来的);
如果有一条路径从结点R至结点M,那么R就称为M的祖先(ancestor),而M称为R的子孙(descendant);
结点M的深度(depth)就是从根结点到M的路径的长度;
树的高度(height)等于最深的结点的深度+1;
任何深度为d的结点的层数(level)都为d,根结点深度为0,层数也为0;
没有非空子树的结点称为叶结点(leaf)或终端结点;
至少有一个非空子树的结点称为分支结点或内部结点(internal node);
结点度:
结点拥有子结点的数量(二叉树中度最大为2,树中则无限制)
满二叉树:
如果一棵二叉树的任何结点,或者是树叶,或者恰有两个非空子树的分支结点,则称为满二叉树;
完全二叉树:
若一棵二叉树最多只有最下面的两层结点度数可以小于2,并且最下面一层的结点都集中在该层最左边的若干位置上,也就是说,自根结点起每一层从左至右的填充,一棵高度为d的树除了第d-1层,每一层都是满的,底层叶结点集中在左边的若干位置上,则称为完全二叉树;
完全二叉树的例子:
二叉树性质
1. 满二叉树定理:非空满二叉树树叶数等于其分支结点数加1
证明:
假设二叉树结点数一共为n,树叶数为l,分支结点数为b,而树叶数+分支结点数=n 即 n=l+b,根据满二叉树的性质有每个分支恰有两个结点,则一共有2b条边,一棵二叉树,除根结点外,每个结点都恰有一条边连接父结点,所以有n-1条边,那么2b=n-1 => 2b= l+b-1 => b+1=l
2. 满二叉树定理的推论:一棵非空二叉树空子树的数目等于其结点数目加1
证明1:
设二叉树T,将其所有空子数换为叶结点,把新的二叉树记为T’,所有原来树T的结点现在是树T’的分支结点。
根据满二叉树定理,新添加的叶结点数目等于树T的结点数目加1,而每个新添加的叶结点对应树T的一棵空子树,因为树T中空子树的数目等于树T中结点数目加1
证明2:
根据定义,二叉树T中每个结点都有两个子结点指针(空或非空),因此一个有n个结点的二叉树有2n个子结点指针,除根结点外,共有n-1个结点,它们都由其父结点中相应指针指引而来,换句话说,就有n-1个非空子结点指针,既然子结点指针数为2n,则其中有n+1个为空(指针),也就是一棵非空二叉树空子树的数目等于其结点数目加1
3. 任何一棵二叉树,度为0的结点比度为2的结点多一个
证明:
设树的结点数为n,其中度为0、1、2的结点数为n0、n1、n2,有n=n0+n1+n2,设边数为e。因为除根以外,每个结点都有一条边进入,故n=e+1。由于这些边是由度为1和2的结点射出的,因为e=n1+2n2,所以有n=e+1=n1+2n2+1,所以n0=n2+1
4. 二叉树的第i层(根为第0层)最多有2i个结点
5. 高度为k(深度为k-1)的二叉树至多有2k-1个结点
(只有一个根结点的二叉树的高度为1,深度为0)
6. 有n个结点的完全二叉树的高度为log2n+1(深度为log2n)
假设该完全二叉树的深度为 k,则根据完全二叉树的定义和性质 2有:
2 ^(k-1)-1< n ≤2^k-1 或 2^(k-1)≤ n <2^k
所以有:k-1≤ log2n<k
又因为 k是整数,所以,k= [log2n]+1
完全二叉树的下标公式
公式中 i 表示结点的索引,n 表示二叉树结点总数
当 i != 0 时,Parent(i)=[(i-1)/2](向下取整);
当 2i + 1 < n 时,LeftChild(i) = 2i + 1;
当 2i + 2 < n 时,RightChild(i) = 2i + 2;
当 r为偶数 且 0 <= r <= n-1 时,LeftSibling(i) = r - 1;
当 r为奇数 且 r + 1 < n 时,RightSibling(i) = r + 1;
周游二叉树
遍历
系统地访问二叉树中地结点,每个结点正好被访问到一次
方法
前序遍历 - preorder traversal – 根 左 右
访问根结点;前序遍历左子树;前序遍历右子树
中序遍历 - inorder traversal – 左 根 右
中序遍历左子树;访问根结点;中序遍历右子树
后序遍历 - inorder traversal – 左 右 根
后序遍历左子树;后序遍历右子树;访问根结点
举例:
前序遍历算法
template <class Elem>
void preorder(BinNode<Elem>* subroot)
{
if(subroot == NULL) return; //空树,不作处理
visit(subroot); // Perform whatever action is desired 即 将subroot结点标记为已遍历
preorder(subroot->left());
preorder(subroot->right());
}
仅知二叉树的先序序列、中序序列或后序序列不足以建树,而已知 先序+中序序列 / 后序+中序序列的话,就可以确定一棵二叉树。
思路:
先序的第一个元素和后序的最后一个元素是当前子树的根,然后遍历中序序列,找到左右子树的分界线,递归建左子树和右子树。
如先序+中序:
例:
二叉树的实现
二叉树的存储
使用指针实现二叉树
二叉链表(最常用)
class TNode:public BinNode<E> {
private:
Key k;
E it;
TNode* lc;
TNode* rc;
......
}
优点:运算方便;
缺点:空指针太多
图例:
带父指针的三重链表
class TNode:public BinNode<E> {
private:
Key k;
E it;
TNode* lc;
TNode* rc;
TNode* father;
......
}
优点:在某些经常要回溯到父结点的应用中很有效
图例:
数组 – 完全二叉树
a. 完全二叉树可使用顺序存储,按照二叉树的层次周游次序存储在一个数组中
优点:简单,节省空间
图例:
顺序存储:ABCDEFGHIJKL
b.非完全二叉树可以置空值后转换为完全二叉树存储
图例(图中蓝色结点均为空):
顺序存储:CEDJFX//K/G/I/L
二叉检索树 / 二叉搜索树 / 二叉查找树 / Binary Search Tree
定义
二叉检索树 或者为空,或者是 满足下列条件的非空二叉树:
(1)若左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若右子树非空,则右子树上所有结点的值均大于或等于根结点的值;
(3)左右子树本身又各是一棵二叉检索树
(也可以是左子树小于等于,右子树大于)
性质
按照中序周游将各节点打印出来,将得到由小到大的序列
也就是说
二叉检索树得到的中序序列是一个从小到大排序好的序列
BST 图例:
检索
其检索效率在于只需检索两个子树之一
过程:
· 从根节点开始,在二叉检索树中检索 值 K
· 若根结点存储的值为 K,则检索结束;
· 若 K 小于 根结点的值,则只需要检索左子树;
· 若 K 大于 根结点的值,则只需要检索右子树;
· 持续上述过程直接找到 K 或 找到了一个树叶
· 如果遇上叶子结点还未发现 K,那么 K 就不在该二叉检索树中
代码:
E find(const Key& k) const {
return findhelp(root, k);
}
E BST<Key,E>::findhelp(BSTNode<Key,E>* root, const Key& k) const {
if(root == NULL) return NULL; // Empty Tree, not found
if(k < root->key())
return findhelp(root->left(),k); // check left
else if(k > root->key())
return findhelp(root->right(),k); // check right
return root->element(); // found
}
```
<br>
**插入:**
```cpp
void insert(const Key& k, const E& e) {
root = inserthelp(root, k, e);
nodeCount++;
}
BSTNode<Key,E>* BST<Key,E>::inserthelp(BSTNode<Key,E>* root, const Key& k, const E& it) {
if(root == NULL) // Empty tree: create node
return new BSTNode<Key,E>(k,it,NULL,NULL);
if(k < root->key())
root->setLeft(inserthelp(root->left(),k,it));
else
root->setRight(inserthelp(root->right(),k,it);
return root; // return tree with node inserted
}
图例:
删除
原理:
删除子树中最小值图示:
BSTNode<Key,E>* BST<Key,E>::deletemin(BSTNode<Key,E>* subroot) {
if (subroot->left() == NULL) { // found min
return subroot->right();
}
else { // Continue left
subroot->setLeft(deletemin(subroot->left()));
return subroot;
}
}
执行过程如下:
deletemin(10);
10->left() != NULL --> 10->setleft(deletemin(5));
deletemin(5);
5->left() == NULL --> return 5->right(); // 即 return 9
10->setleft(9);
假如要删除的是右子树中最小值结点(值为37):
E remove(const Key& k) {
E temp = findhelp(root, k); // Firstly, find it
if(temp != NULL) {
root = removehelp(root, k);
nodeCouunt--;
}
return temp;
}
BSTNode<Key,E>* BST<Key,E>::removehelp(BSTNode<Key,E>* subroot, const Key& k) {
if (subroot == NULL) return NULL; // k is not in tree
else if (k < subroot->key())
subroot->setLeft(removehelp(subroot->left(), e));
else if (k > subroot->key())
subroot->setRight(removehelp(subroot->right(), e));
else { // Found it: remove it
BSTNode<Key,E>* temp = subroot;
if (subroot->left() == NULL) { // only a left child
subroot = subroot->right();
delete temp;
}
else if (subroot->right() == NULL) { // only a right child
subroot = subroot->left();
delete temp;
}
else { // Both children are non-empty
temp = getmin(subroot->right()); // 找删除的结点的中序后继结点
subroot->setElement(temp->element());
subroot->setKey(temp->Key);
subroot->setRight(deletemin(subroot->right()));
delete temp;
}
}
return subroot;
}
```
执行过程如下:
subroot == 37; //左右均不为空
temp = getmin(42) = 40;
subroot = 40; //根结点的值在此处变为40,即将右子树的最小值换为了其中序遍历的后继结点的值
subroot -> setLeft(deletemin(42));
deletemin(42);
42 -> setLeft(deletemin(40));
40->left() == NULL --> return 40->right(); // 即 return NULL
42->setleft(NULL);