9.1 树与二叉树
9.1.1 树的性质
- 树可以没有结点,这种情况下把树称为空树(empty tree)
- 树的层次(layer)从根结点开始算起,即根结点为第一层,根结点子树的根结点为第二层,以此类推。
- 把结点的子树棵数称为结点的度(degree),而树中结点的最大的度称为树的度(也称为树的宽度),例如图9-1中的三棵树的度分别为2、3、5.
- 由于一条边连接两个结点,且树中不存在环,因此对有n个结点的树,边数一定是n-1,且满足连通、边数等于顶点数减1的结构一定是一棵树。
- 叶子结点被定义为度为0的结点,因此当树中只有一个结点(即只有根结点)时,根节点也算作叶子结点。
- 0结点的深度(depth)是指从根结点(深度为1)开始自顶向下逐层累加至该结点时的深度值;结点的高度(height)是指从最底层叶子结点(高度为1)开始自底向上逐层累加至该结点时的高度值。树的深度是指树中结点的最大深度,树的高度是指树中结点的最大高度。
- 多棵树组合在一起称为森林(forest),即森林是若干棵树的集合。
9.1.2 二叉树的递归定义
- 要么二叉树没有根节点,是一棵空树。
- 要么二叉树由根节点、左子树、右子树组成,且左子树和右子树都是二叉树。
一个递归函数必须存在两个概念:递归边界和递归式。
二叉树中任何一个节点的左子树既可以是一棵空树,也可以是一棵有左子树和右子树的二叉树;节点的右子树既可以是一棵空树,又可以是一棵有左子树和右子树的二叉树。
区分度为2的树与二叉树
对树来说,节点的子树是不分左右顺序的,因此度为2的树只能说明树中每个节点的子节点个数不超过2。二叉树虽然也满足每个节点的子结点个数不超过2,但它的左右子树是严格区分的,不能随意交换左子树和右子树的位置。
满二叉树与完全二叉树
- 满二叉树:每一层的结点个数都达到了当层能达到的最大结点数。
- 完全二叉树:除了最下面一层之外,其余层的结点个数都达到了当层能达到的最大结点数,且最下面一层只从左至右连续存在若干结点,而这些连续结点右边的结点全部不存在。
9.1.3 二叉树的存储结构与基本操作
1. 二叉树的存储结构
一般来说,二叉树使用链表定义。
struct Node{
typename data ; //数据域
Node* lchild ; //指向左子域
Node* rchild ; //指向右子域
};
如果需要新建节点
Node* newNode( int v ){
Node* node = new Node ;
node->data = v ;
node->lchild = node->rchild = NULL ; //初始状态下没有左右孩子
return node ; //返回新建节点的地址
}
2. 二叉树节点的查找与修改
查找操作是指在给定数据域的条件下,在二叉树中找到所有数据域为给定数据域的结点,并将它们的数据域修改为给定的数据域。
void search( Node* root , int x , int newdata ){
if( root == NULL ){
return ; //空树(递归边界) ,直接返回
}
if( root->data == x ){ //找到数据域为x的节点,将数据域修改为newdata
root->data = newdata ;
}
search( root->lchild , x , newdata ) ;
search( root->rchild , x , newdata ) ;
}
3. 二叉树节点的插入
二叉树结点的插入位置就是数据域在二叉树中查找失败的位置。而由于这个位置是确定的,因此在递归查找的过程中一定是只根据二叉树的性质来选择左子树或右子树中的一棵子树进行递归,且最后到达空树(死胡同)的地方就是查找失败的地方,也就是结点需要插入的地方。
void insert( Node* &root , int x ){
if( root == NULL ){ //空树,查找失败,也即可插入位置
root = newNode(x) ;
return ;
}
if( 由二叉树性质,x应插在左子树 ){
insert( root->lchild , x ) ;
}
else{
insert( root->rchild , x ) ;
}
}
在上面代码中,很关键的一点是根节点指针root使用了引用&。这么做的原因是,在insert函数中新建了节点,并把新节点的地址赋给了当层的root。如果不使用引用的话,root=newNode(x)这个语句对root的修改就无法作用到原变量上去,也就不能把新节点接到二叉树上面。
那为什么search函数不需要加引用呢?
这是因为search函数中修改的是指针root指向的内容,而不是root本身。
4. 二叉树的创建
Node* create( int data[] , int n ){
Node* root = NULL ;
for( int i = 0 ; i < n ; i++ ){
insert( root , data[i] ) ;
}
return root ;
}
5 完全二叉树的存储结构
对完全二叉树当中的任何一个结点(设编号为x),其左孩子的编号一定是2x,而右孩子的编号一定是2x+1。也就是说,完全二叉树可以通过建立一个大小为pow(2,k)的数组来存放所有结点的信息,其中k为完全二叉树的最大高度,且1号位存放的必须是根结点。
除此之外:
- 该数组中元素存放的顺序恰好为该完全二叉树的层序遍历序列。
- 判断某个节点是否为叶节点的标志为:该节点(记下标为root)的左子树的编号为root*2大于总节点个数n
- 判断某个节点是否为空节点的标志为:该节点下标root大于节点总个数n。