1、二叉树的结构
二叉树是由一个储存数据的变量和两个指向子树的指针构成的,定义在一个结构体中。其一般形式为:
typedef struct BiTree
{
char data;
struct BiTree *lchild;
struct BiTree *rchild;
}BiTree,*BiNode;
BiNode即结构体变量的指针,BiTree是结构体变量。在创建二叉树时,为每个结点动态分配内存时要用到这两个标识符,例如: BiNode root=(BiNode)malloc(sizeof(BiTree))。
2、二叉树的建立
二叉树一般使用递归的方式建立,递归调用函数来创建自己的左右子树。
BiNode create()//先序建立二叉树
{
BiNode T;
char a;
scanf("%c",&a);
if(a=='.') return NULL;//当输入.时表示这个结点为空,而且没有左右子树。
else
{
T=(BiNode)malloc(sizeof(BiTree));//动态分配内存
T->data=a;
T->lchild=create();//创建左子树
T->rchild=create();//创建右子树
}
return T;
}
若想中序或后序创建,则只需改变函数中 T->data=a; T->lchild=create(); T->rchild=create(); 这三条语句的顺序,先给T->data=a在先的话是先序,在中间的话是中序,在最后的话是后序。
3、二叉树的遍历
二叉树一般有4种遍历方法,即先序、中序、后序、层序。
先序的遍历顺序是根节点->左子树->右子树。
中序的遍历顺序是左子树->根节点->右子树。
后序的遍历顺序是右子树->根节点->左子树。
层序的遍历顺序是按层顺次遍历。
先序、中序、后序的代码基本相同
void pre(BiNode root)//先序遍历二叉树
{
if(root)
{
printf("%c ",root->data);
pre(root->lchild);
pre(root->rchild);
}
}
void mid(BiNode root)//中序遍历二叉树
{
if(root)
{
mid(root->lchild);
printf("%c ",root->data);
mid(root->rchild);
}
}
void post(BiNode root)//后序遍历二叉树
{
if(root)
{
post(root->lchild);
post(root->rchild);
printf("%c ",root->data);
}
}
例如这个简单的二叉树:
当我们先序建立二叉树时,应输入:ABDF..GH..I..E..C..,这三种遍历的结果为:
而层序遍历较为复杂,可以用队列来实现,队列的特点是先进先出。我们先构造一个队列,先将根节点入队列,之后每个出队列的结点都要判断其左右子树是否存在,若存在则入队列。代码如下:
void level(BiNode root)//二叉树的层次遍历,运用队列,每层的结点挨个先进先出。
{
BiNode queue[20],pTemp;
int cur=0,pre=0;//cur表示当前入队列的结店,pre表示当前出队列的结点
queue[cur++]=root;
while(cur!=pre)
{
pTemp=queue[pre++];
printf("%c ",pTemp->data);
if(pTemp->lchild!=NULL) queue[cur++]=pTemp->lchild;
if(pTemp->rchild!=NULL) queue[cur++]=pTemp->rchild;
}
}
层序遍历上面的二叉树的运行结果为:
4、特殊二叉树
1、二叉排序树
1.1 特点及构造方法
二叉排序树的特点是:若一个结点的左子树非空,则左子树的值一定小于结点的值;若一个节点的右子树非空,则右子树的值一定大于结点的值。
例如这颗简单二叉排序树:
其创建过程也不复杂,对于每个数据,都判断它与根节点的大小关系,若大于根节点,则作为根节点的右子树,若小于根节点,则作为根节点的左子树。则二叉排序树的中序遍历一定是严格递增的。代码如下:
BiNode BSTInsert(BiNode T,int key)
{
if(!T)//若为空,则创建这个结点
{
T=(BiNode)malloc(sizeof(BiTree));
T->data=key;
T->lchild=T->rchild=NULL;
}
else
{
if(T->data<key)
T->rchild=BSTInsert(T->rchild,key);
else if(T->data>key)
T->lchild=BSTInsert(T->lchild,key);
}
return T;
}
BiNode Insert(BiNode T,int data[],int n)
{
int i;
for(i=0;i<n;i++)
T=BSTInsert(T,data[i]);
return T;
}
int main()
{
int num[9]={8,3,10,13,14,1,6,7,4};
BiNode T=NULL;
T=Insert(T,num,9);
printf("\n中序遍历:");
mid(T);
return 0;
}
将一个数组{8,3,10,13,14,1,6,7,4}作为数据构造一棵二叉排序树,可以得到上图中的结果,对他进行中序遍历的结果为:
得到了正确的结果。也可以在二叉排序树构造成功后直接使用BSTInsert函数来插入元素,例如在上面的例子里插入5:
int main()
{
int num[9]={8,3,10,13,14,1,6,7,4};
BiNode T=NULL;
T=Insert(T,num,9);
printf("中序遍历:");
mid(T);
BSTInsert(T,5);
printf("\n插入后:");
mid(T);
return 0;
}
运行结果为:
1.2 二叉排序树的查找
根据二叉树的特点,我们可以轻松的找出二叉排序树中的最小、最大元素和特定元素。
寻找最小、最大元素的方法是相同的。根据二叉树的特点,若左右子树不为空,则根节点的值一定大于左子树的值、小于右子树的值,那么这棵树的根节点的最左子树和最右子树即为最小、最大元素。代码如下:
BiNode Searchmin(BiNode T)//寻找二叉排序树中的最小元素
{
if(T==NULL)
return NULL;
if(T->lchild==NULL)
return T;
else return Searchmin(T->lchild);//不断搜索他的左子树
}
BiNode Searchmax(BiNode T)//寻找二叉排序树中的最大元素
{
if(T==NULL)
return NULL;
if(T->rchild==NULL)
return T;
else return Searchmax(T->rchild);//不断搜索他的右子树
}
而查找特定元素也利用了排序树的特点。将结点的值与待查值做比较,若结点值大,则说明待查值在结点的左子树;若结点值小,则说明待查值在结点的右子树,然后递归或不递归地查询结点的左子树或右子树即可。递归查询的代码如下:
BiNode Search_1(BiNode T,int key)//递归查找特定元素
{
if(T==NULL)
return NULL;
if(key>T->data)
return Search_1(T->rchild,key);//查找右子树
else if(key<T->data)
return Search_1(T->lchild,key);//查找左子树
else return T;//找到则返回指针
}
在此基础上,我们可以省掉递归过程,用while循环来实现结点的遍历。非递归查找代码如下:
BiNode Search_2(BiNode T,int key)//非递归查找特定元素
{
BiNode p=T;
while(p)
{
if(p->data==key)//找到就返回结点
return p;
else p=(p->data>key)?p->lchild:p->rchild;//判断下一个要查找的是左子树还是右子树。
}
return NULL;//找不到则返回空
}
1.3 删除二叉排序树的一个特定结点
对于一般的二叉树来说,删去树中的一个结点是没有意义的,因为它将使以被删除的结点为根的子树变成森林,破坏了整棵树的结构。但是,对于二叉排序树,删去树上的一个结点相当于删去有序序列中的一个记录,只要在删除某个结点后不改变二叉排序树的特性即可。
删除节点p的一般过程是:
1、在二叉排序树中找到要删除的那个结点p。
2、若p有左子树,则找到左子树的最右子树r,用r来代替p,将r的左子树作为r父亲的右子树。
3、若p没有左子树,则用p的右子树r代替p。
第一步我们可以用一个函数,先根据二叉排序树的特点找到要删除的结点。找到后,用另一个函数来实现第二步或第三步。
明确思路后,应考虑如何完成用r代替p这一操作,若移动r和p的指针,不仅麻烦,而且移来移去容易出错。仔细思考一下,我们删除的实质上是r,并不是p,则更好的办法是将r的值赋给p,然后删除掉r结点。代码如下:
BiNode Delete(BiNode T,int key)//删除二叉排序树中的特定元素
{
if(T->lchild)//如果存在左子树
{
BiNode p=T->lchild;
BiNode parent=T->lchild;
while(p->rchild!=NULL)//找到左子树的最右子树
{
parent=p;
p=p->rchild;
}
T->data=p->data;//将要删除的结点的值覆盖掉
if(parent!=p)//如果找到的最右子树不是T的左子树,则把最他的左子树作为他的父结点的右子树
parent->rchild=p->lchild;
else T->lchild=p->lchild;//否则T的左子树指向p的左子树
free(p);//清空p的内存
return T;
}
else//如果不存在左子树,则直接返回自己的右子树。
{
BiNode p=T->rchild;
free(T);
return p;
}
}
BiNode DeleteBST(BiNode T,int key)//二叉排序树的删除
{
if(T)
{
if(T->data==key)//若找到要删除的结点,则执行删除操作
T=Delete(T,key);
else if(T->data>key)
T->lchild=DeleteBST(T->lchild,key);
else T->rchild=DeleteBST(T->rchild,key);
}
return T;
}
用{8,3,10,13,14,1,6,7,4}来测试一下这段代码:
完全符合预期。