树是一种重要的数据结构,关于树的基本概念,如根结点、子结点、树高等,我们不难根据名字理解其定义,因此我们不再过多介绍。下面我们来看看二叉树,并为二叉查找树的引入进行准备。
二叉树是每个结点最多有两个子树的树。此外,如果每个非叶子结点的的结点都有两个子结点,那么这样的二叉树为满二叉树。如果一个二叉树的全部结点都满足:一个结点有右子结点,那么这个结点一定有左子结点,那么这个二叉树为完全二叉树。
下面给出三个例子,它们分别是满二叉树和两个完全二叉树。
从上述三个二叉树中我们不难发现一个完全二叉树有一个奇妙的性质:若一个二叉树的父结点为k,那么它左子结点为2k,右子结点为2k+1。如果一个子结点的编号是k,那么其父结点编号为k/2。
这种奇妙的性质给了我们一种很好的方法去模拟一个完全二叉树:数组。data[1]为根结点,左子结点为data[2],右子结点为data[3]……这样下去,我们就能通过数组的下标来表示这个树的关系。这样对所有二叉树都适用,对于不存在的结点,只要标记其对应的数组元素即可。但是,若如此做,会浪费大量的空间。因此我们在这里不实现这种方法。
下面我们介绍二叉查找树的定义:
1.若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2.若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3.左、右子树也分别为二叉排序树;
4.没有键值相等的结点。
不难发现,二叉查找树是将所有元素“劈成”两半,按照大小的顺序摆放好位置,提高了二叉树的有序程度。因此它也有着更高的效率。今天我们尝试建立一个二叉查找树。
首先来定义一个结构体,表示出每一个结点所需要的数据。
typedef struct node
{
int data;
struct node *father; //指向父结点
struct node *lch; //指向左子结点
struct node *rch; //指向右子结点
} Node, *ptrNode;
接下来是定义一个create函数,这里应当是不难理解的。
ptrNode create(int data)
{
ptrNode temp = (ptrNode)malloc(sizeof(Node));
temp->data = data; //存储数据
temp->lch = NULL; //将左右子结点都设为空
temp->rch = NULL;
temp->father = NULL; //根结点没有父结点
return temp;
}
此外,我们还需要一个向这个二叉查找树中添加结点的函数。
void addnode(ptrNode ptr, int data)
{
if (data < ptr->data) //如果数据小于该结点的数据,那么放入左子树
{
if (ptr->lch == NULL) //如果没有左子树,那么给这个结点添加左子结点
{
ptr->lch = create(data);
ptr->lch->father = ptr; //设置刚刚创建的结点的父结点
}
else //如果有左子树,那么递归地继续查找,直到找到没有子结点的,关于此数据的正确的位置
{
addnode(ptr->lch, data);
}
}
else //在数据大于该结点的情况下,放入右子树,下述内容与放入左子树的过程类似
{
if (ptr->rch == NULL)
{
ptr->rch = create(data);
ptr->rch->father = ptr;
}
else
{
addnode(ptr->rch, data);
}
}
}
在完成了这两个函数之后,我们可以尝试输入自己的数据创建一个二叉查找树。
int main()
{
ptrNode tree, temp;
int data;
//以下是creat和addnode模块
scanf("%d", &data);
tree = create(data);
while (1)
{
scanf("%d", &data);
if (data == 0)//因为作演示,故设置为输入0结束创建。
break;
addnode(tree, data);
}
return 0;
}
当然,现在的我们创建完这个二叉查找树后什么都不能做,下一次我们会介绍查找元素、删除元素、求树高和遍历元素等操作。