二叉树的存储结构
一般来说,二叉树使用链表来定义。和普通链表的区别是,由于二叉树的每个结点有两条出边,因此指针变成了两个-分别指向左子树的根节点地址和右子树的根节点的地址。如果某个子树不存在,则指向NULL,其他地方和普通链表完全相同,因此又把这种链表叫做二叉链表。
定义方式如下:
struct node{
typename data;//数据域
node *lchild;//指向左子树的根节点的指针
node *rchild;//指向右子树的根节点的指针
};
由于在二叉树中建树前根节点不存在,因此其地址一般设为NULL
node *root=NULL;
而如果需要新建结点(例如在二叉树中插入结点的时候),就可以使用下面的函数
node *newNode(int v){
//生成一个新结点,v为结点的权值
node *Node=new node;//申请一个node型变量的地址空间
Node->data=v;//结点权值为v
Node->lchild=NULL;
Node->rchild=NULL;//初始状态下没有左右孩子
return Node;//返回新建结点的地址
}
二叉树的常用操作有以下几个:二叉树的建立、二叉树结点的查找、修改、插入与删除。
二叉树结点的查找、修改
查找操作是在给定数据域的条件下,在二叉树中找到所有数据域为给定数据域的结点,并将他们的数据域修改为给定的数据域
需要使用递归来完成查询与修改操作。对二叉树进行递归时,递归式就是只对当前结点的左子树和右子树分别递归,递归边界是当前结点为空时返回。例如查找修改操作,先判断当前结点是否是需要查找的结点,如果是,则对其进行修改操作;如果不是,则分别往该结点的左子树和右子树递归,直到当前结点为NULL为止。
示例代码如下:数据域以int为例:
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);//右子树
}
二叉树结点的插入
由于二叉树的形态很多,因此在题目不说明二叉树的特点时很难给出结点的插入的具体方法,但是又必须认识到,结点的插入位置一般取决于数据域需要在二叉树中存放的位置(这与二叉树本身的性质有关)且对给定的结点来说,它在二叉树的插入位置只会有一个(否则题目本身就具有不确定性)。因此可以得到这样的结论,即二叉树结点的插入位置就是数据域在二叉树中查找失败的位置。而由于这个位置是确定的,因此在递归查找的过程中一定是只根据二叉树的性质来选择左子树和右子树中的一颗子树进行递归,且最后达到递归边界就是查找失败的地方,也就是结点需要插入的地方,由此可以得到二叉树的插入代码:
void insert(node *&root,int x){
if(root==NULL){
//空树,说明查找失败,也就是需要插入的位置(递归边界)
root=newNode(x);
return;
}
if(由于二叉树的性质,插入左子树){
insert(root->lchild,x);//往左子树搜索,递归式
}else{
insert(root->rchild,x);//往右子树搜索(递归式)
}
}
在上述代码中,很关键的一点是根节点指针root使用了引用&。 引用的作用就是在函数中修改root会直接修改原变量,这么做的原因是,在insert函数中新建了结点,并把新结点的地址赋给了当层的root,如果不使用引用,root=new node这个语句对root的修改就无法作用到原变量(即上一层的root->lchild和root->rchild)上去,也就不能把结点接到二叉树上面,因此insert函数必须加引用。
那么为什么前面的search函数不需要加引用呢?这是因为search函数中修改的是指针root指向的内容,而不是root本身,而对指针实现的结点内容的修改是不需要加引用的
那么,如何判断是否需要加引用呢?一般来说,如果函数中需要新建结点,即对二叉树的结构作出修改,就需要加引用,如果只是修改当前结点已有的内容,或仅仅是遍历树,就不需要加引用。至于判断不出来的情况,不妨直接试一下加引用和不加引用的区别
最后再提醒一句,在新建结点之后,务必令新结点的左右指针域均为NULL。表示这个结点暂时没有左右子树
二叉树的创建
二叉树的创建其实就是二叉树结点的插入过程,而插入所需要的结点数据域一般都会由题目中给出,因此比较常用的写法是把需要插入的数据存储在数组中,然后再将它们使用insert函数一个个插入二叉树中,并最终返回根节点的指针root。也可以直接再建立二叉树的过程中边输入数据边插入结点。代码如下:
完全二叉树的存储结构
对完全二叉树来说,除了采用二叉链表的存储结构外,还可以有更方便的存储方法,通过观察可以发现,对完全二叉树当中的任何一个结点(设编号为x),其左孩子的编号一定是2x,而右孩子的编号一定是2x+1.也就是说,完全二叉树可以通过建立大小为2^k的数组来存储所有结点的信息,其中k为完全二叉树的最大高度,且1号必须存放的是根节点(数组下标从1开始,而不是0)z这样就可以用数组下标来表示结点的编号,且左孩子和右孩子的编号都可以直接计算到
事实上,如果不是完全二叉树,也可以视其为完全二叉树。即把空结点也进行实际的编号。但是会造成空间的浪费,一般很少使用。若题目中规定是完全二叉树,那么数组大小只需要设为结点上限个数加1即可,这样会大大减少编码复杂度
除此之外,该数组中元素存放的顺序恰好为该完全二叉树的层次遍历序列
判断某个结点是否为叶节点的标志为:该结点(记下标为root)的左孩子的编号2*root大于结点的总个数n;
判断某个结点是否为空结点的标志为该结点的下标root大于结点总个数n。