二叉树为连接于一对二叉树的一个外部节点或内部节点,这两棵二叉树分别称为这个节点的左子树和右子树。这个定义表明,二叉树本身就是一个抽象的数学概念。当我们制定计算机表示方式时,就是在制定一个具体实现。这种情形与用float表示实数,用int表示整数等没有区别。当我们绘制一颗树,根节点通过与左边的左子树以及右边的右子树连接,此时,就是在选择一种方便的具体表示方式。
我们开发那些应用和操作二叉树的程序时,最常用的具体表示法是内部节点带两个链接的结构(左链接与右链接)。这些结构与链表相似,但它们的每个节点有2个链接,而不是一个。空链接与外部节点对应。
定义二叉树的接口如下:
/* 节点结构定义*/
typedef struct node *link;
struct node{
Item item;
link l,r;
};
/* 常用操作定义*/
link NEW(Item item,link l,link r);
int count(link h);
int height(link h);
void preorderTraverse(link h,void (*visit)(link));
void printnode(char c,int h);
void show(link x,int h);
实现如下:
#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"
/* construct a new node*/
link NEW(Item item,link l,link r)
{
link x=(link)malloc(sizeof(*x));
x->item=item;
x->l=l;
x->r=r;
return x;
}
/* get the number of the nodes*/
int count(link h)
{
if(h==NULL)
return 0;
return count(h->l)+count(h->r)+1;
}
/* get the height of the tree*/
int height(link h)
{
int u,v;
if(h==NULL)
return -1;
u=height(h->l);
v=height(h->r);
if(u>v)
return u+1;
else
return v+1;
}
/* preorder traverse*/
void preorderTraverse(link h,void (*visit)(link))
{
if(h==NULL)
return;
preorderTraverse(h->l,visit);
preorderTraverse(h->r,visit);
}
/* print the tree node*/
void printnode(char c,int h)
{
int i;
for(i=0;i<h;i++)
printf(" ");
printf("%c/n",c);
}
/* print a subtree which root is x and in level h*/
void show(link x,int h)
{
if(x==NULL)
{
printnode('*',h);
return;
}
show(x->r,h+1);
printnode(x->item,h);
show(x->l,h+1);
}
二叉树的应用广泛,这里举几个例子。
构建锦标赛:
下面的程序将数组a[0],...,a[r]分成两部分a[0],...,a[m]和a[m+1],...,a[r],分别对这两部分构建锦标赛(按递归方式),在对整个数组生成一个锦标赛。
link max(Item a[],int l,int r)
{
int m=(l+r)/2; // get the middle number
Item u,v;
link x=NEW(a[m],NULL,NULL); // construct a new node
if(l==r) // left equals to right means they are the same one
return x;
x->l=max(a,l,m); // return max of the left part
x->r=max(a,m+1,r); // return max of the right part
u=x->l->item;
v=x->r->item;
if(u>v)
x->item=u;
else
x->item=v;
//show(x,0); // print the tree
printf("/n/n");
return x;
}
构建解析树:
利用求前缀表达式值的同样策略,下面程序根据一个前缀表达式构建一颗解析树。为了简单起见,我们假定操作数为单个字符。每个递归调用创建一个新节点,它包含从输入(作为符号)获取的下一个字符。如果符号为操作数,则返回新节点;如果是一个运算符,则将左指针和右指针分别指向这两个参数(递归)生成的树。
char *a;int i;
typedef struct Tnode* link;
struct Tnode{
char token;
link l, r;
};
link NEW(char token,link l,link r)
{
link x=(link)malloc(sizeof(*x));
x->token=token;
x->l=l;
x->r=r;
return x;
}
link parse()
{
char t=a[i++];
link x=NEW(t,NULL,NULL);
if((t=='+')||(t=='*'))
{
x->l=parse();
x->r=parse();
}
return x;
}
树的应用还有好多,关键在于我们要将问题抽象成数学模型,然后运用这些模型求解问题。当然,小问题可以不用编写程序来求解,但是面对大量复杂计算的时候我们就可以编制程序来求解它们。这时就要用到数据结构与算法的知识了。最好的情况是我们使用的语言足够灵活,并且能够处理各种各样的数据,比如大整数,浮点数,复数,等等。有些可以通过语言的其他方法实现,比如C中没有复数,我们可以通过定义复数类型并提供接口来实现复数的运算。总之,处理方法多种多样。在好的算法和数据结构的基础上,我们可以再对程序进行包装,做出人机界面,使程序更易于使用。
我们开发那些应用和操作二叉树的程序时,最常用的具体表示法是内部节点带两个链接的结构(左链接与右链接)。这些结构与链表相似,但它们的每个节点有2个链接,而不是一个。空链接与外部节点对应。
定义二叉树的接口如下:
/* 节点结构定义*/
typedef struct node *link;
struct node{
Item item;
link l,r;
};
/* 常用操作定义*/
link NEW(Item item,link l,link r);
int count(link h);
int height(link h);
void preorderTraverse(link h,void (*visit)(link));
void printnode(char c,int h);
void show(link x,int h);
实现如下:
#include <stdio.h>
#include <stdlib.h>
#include "Tree.h"
/* construct a new node*/
link NEW(Item item,link l,link r)
{
link x=(link)malloc(sizeof(*x));
x->item=item;
x->l=l;
x->r=r;
return x;
}
/* get the number of the nodes*/
int count(link h)
{
if(h==NULL)
return 0;
return count(h->l)+count(h->r)+1;
}
/* get the height of the tree*/
int height(link h)
{
int u,v;
if(h==NULL)
return -1;
u=height(h->l);
v=height(h->r);
if(u>v)
return u+1;
else
return v+1;
}
/* preorder traverse*/
void preorderTraverse(link h,void (*visit)(link))
{
if(h==NULL)
return;
preorderTraverse(h->l,visit);
preorderTraverse(h->r,visit);
}
/* print the tree node*/
void printnode(char c,int h)
{
int i;
for(i=0;i<h;i++)
printf(" ");
printf("%c/n",c);
}
/* print a subtree which root is x and in level h*/
void show(link x,int h)
{
if(x==NULL)
{
printnode('*',h);
return;
}
show(x->r,h+1);
printnode(x->item,h);
show(x->l,h+1);
}
二叉树的应用广泛,这里举几个例子。
构建锦标赛:
下面的程序将数组a[0],...,a[r]分成两部分a[0],...,a[m]和a[m+1],...,a[r],分别对这两部分构建锦标赛(按递归方式),在对整个数组生成一个锦标赛。
link max(Item a[],int l,int r)
{
int m=(l+r)/2; // get the middle number
Item u,v;
link x=NEW(a[m],NULL,NULL); // construct a new node
if(l==r) // left equals to right means they are the same one
return x;
x->l=max(a,l,m); // return max of the left part
x->r=max(a,m+1,r); // return max of the right part
u=x->l->item;
v=x->r->item;
if(u>v)
x->item=u;
else
x->item=v;
//show(x,0); // print the tree
printf("/n/n");
return x;
}
构建解析树:
利用求前缀表达式值的同样策略,下面程序根据一个前缀表达式构建一颗解析树。为了简单起见,我们假定操作数为单个字符。每个递归调用创建一个新节点,它包含从输入(作为符号)获取的下一个字符。如果符号为操作数,则返回新节点;如果是一个运算符,则将左指针和右指针分别指向这两个参数(递归)生成的树。
char *a;int i;
typedef struct Tnode* link;
struct Tnode{
char token;
link l, r;
};
link NEW(char token,link l,link r)
{
link x=(link)malloc(sizeof(*x));
x->token=token;
x->l=l;
x->r=r;
return x;
}
link parse()
{
char t=a[i++];
link x=NEW(t,NULL,NULL);
if((t=='+')||(t=='*'))
{
x->l=parse();
x->r=parse();
}
return x;
}
树的应用还有好多,关键在于我们要将问题抽象成数学模型,然后运用这些模型求解问题。当然,小问题可以不用编写程序来求解,但是面对大量复杂计算的时候我们就可以编制程序来求解它们。这时就要用到数据结构与算法的知识了。最好的情况是我们使用的语言足够灵活,并且能够处理各种各样的数据,比如大整数,浮点数,复数,等等。有些可以通过语言的其他方法实现,比如C中没有复数,我们可以通过定义复数类型并提供接口来实现复数的运算。总之,处理方法多种多样。在好的算法和数据结构的基础上,我们可以再对程序进行包装,做出人机界面,使程序更易于使用。