目录
1.背景介绍
1.1 树的介绍
和之前的链表,栈,队列不同,树是一种非线性结构。每个树都由父节点和子节点构成。最初始的父节点被称为根节点,除去根节点之外的所有节点都有其对应的父节点。
1.2 二叉树的优势
在前面几章我们学习了链表的相关使用,然而如果有大量的输入数据,用普通链表进行访问时需要从头节点开始挨个查询,查询速度就很慢。
为了在提高在大量输入数据情况下的查询,插入,删除速度,这里就引出了二叉树这个概念,二叉树是一种特殊的树,每个节点的子节点不能多于两个。二叉树的特殊结构使得在进行以上操作时速度较快。
1.3 二叉查找树的性质
二叉查找树又被称为二叉排序树或者二叉搜索树,它的基本性质是:对于树中的每个节点pNode,它的左子树中的元素值要小于当前节点的元素值,它的右子树中的元素值要大于当前节点的元素值。
即对于任意的节点来说:左子树值< 当前节点值 < 右子树值。
2.二叉查找树的链表实现
二叉查找树的实现需要对链表相关知识有所了解。对链表不是很熟悉的可以看看下面这个连接。
2.1 节点声明
二叉查找树由多个节点组成,因此我们首先需要声明节点,和声明链表节点类似,二叉树节点也有数据域和指针域构成,数据域用来存储数据,指针域用来指向它的子节点。
struct node
{
int element; //数据域
struct node *left; //指向左边的子节点
struct node *right; //指向右边的子节点
}
2.2 建立空的二叉查找树
要建立一棵空树,实际上就是只建立一个根节点,并将他的指针域指向NULL即可。
//建立一棵空树
BOOL init_tree(struct node **pTree)
{
*pTree = (struct node *)malloc(sizeof(struct node));
if(NULL = *pTree)
{
return FALSE; //申请树的根节点内存失败
}
(*pTree)->left = NULL;
(*pTree)->right = NULL;
return TRUE;
}
//测试用例
struct node *pTree = NULL;
bool ret = init_tree(&pTree);
2.3 二叉查找树中的最小值
查找二叉树中的最小值即找到整个二叉树中最左边的节点即可。
int find_MinElement(struct node *pTree)
{
if(NULL == pTree)
{
return 0; //空树,无法查找元素
}
while(NULL != pTree->left)
{
pTree = pTree->left;
}
return pTree->element;
}
2.4 二叉查找树中的最大值
查询二叉平衡树中的最大值和查询最小值的方法一致,只需要不断地查询直到到最右边子节点即可。
int find_MaxElement(struct node *pTree)
{
if(NULL == pTree)
{
return 0; //空树,无法查找元素
}
while(NULL != pTree->left)
{
pTree = pTree->left;
}
return pTree->element;
}
2.5 查找指定元素并返回地址
首先确定思路:查找二叉查找树的元素,实际上利用了二叉查找树的性质:左子节点值 < 当前节点值 < 右子节点值。利用这个性质,我们可以在每个节点处将节点值和查找值做对比,从而快速找到想要查找的元素。
/*函数功能:查找指定元素
*函数参数:pNode-树的根节点地址,element-要查找的元素
*函数返回值:TRUE-查询成功 FALSE-查询失败
bool find_node(struct node *pTree, int element)
{
while(NULL != pTree)
{
if(element == pTree->element)
{
return TRUE; //查找成功,返回该元素
}
else if(element < pTree->element)
{
pTree = pTree->left; //查找元素值小于当前节点元素值,继续向左寻找
}
else
{
pTree = pTree->right; //查找元素值大于当前节点元素值,继续向右寻找
}
}
//查找失败
return FALSE;
}
2.6 在二叉查找树中插入新元素
插入一个新元素的思路如下:
步骤一:在二叉查找树中查找最近的元素的父节点。
步骤二:新建一个节点并将其初始化。
步骤三:将新节点连接在父节点之后。
根据上述思路,我们先编写一个函数,目的是返回离插入节点最近的节点地址。
/*函数功能:查找离插入节点最近的节点
*函数参数:pNode-树的根节点地址,element-要查找的元素
*函数返回值:TRUE-查询成功 FALSE-查询失败
bool find_insert_preNode(struct node* pNode, int element)
{
struct node *pRre = NULL;
while(NULL != pNode)
{
pRre = pNode;
if((element == pNode->element))
{
return FALSE; //要插入的元素已存在,无需插入
}
else if(element < pNode->element)
{
pNode = pNode->left; //查找元素值小于左子节点元素值,继续向左寻找
}
else if(element > pNode->right->element)
{
pNode = pNode->right; //查找元素值大于右子节点元素值,继续向右寻找
}
}
return TRUE;
}
然后再将新节点插入到上一个节点之后即可。
/*函数功能:在二叉树中插入新元素
*函数参数:pTree-树的根节点地址,element-要查找的元素
*函数返回值:TRUE-插入成功,FALSE-插入失败
bool insert_element(struct node *pTree, int element)
{
struct node *pNode = pTree; //不要直接使用作为函数参数的指针
//步骤一:查找要插入节点的上一个节点
if(!find_insert_preNode(pNode, element));
{
return FALSE;//二叉树中已有该元素,无需插入
}
//步骤二:创建新节点并初始化
struct node *pNew = NULL;
pNew = (struct node *)malloc(sizeof(struct node));
if(NULL == pNew)
{
return FALSE; //新节点申请内存失败
}
pNew->element = element;
pNew->left = NULL;
pNew->right = NULL;
//步骤三:将新节点连接到上一个节点后面
if(element < pNode->element)
{
pNode->left = pNew;
}
else
{
pNode->right= pNew;
}
return TRUE;
}
2.7 在二叉查找树中删除元素
要删除二叉树中的指定元素的思路如下:
步骤一:查找删除节点和删除节点的父节点的位置。
步骤二:将需要删除节点的父节点和子节点连接,并free需要删除的节点。
根据以上的思路,我们先编写一个函数实现在二叉树中查找指定元素并返回它的父节点。
/*函数功能:查找指定元素的上一个节点
*函数参数:pNode-树的根节点地址,element-要查找的元素
*函数返回值:TRUE-查找成功 FALSE-查找失败
bool find_preNode(struct node* pNode, int element)
{
while(NULL != pNode)
{
//左右子节点有一个存在
if((NULL != pNode->left) || (NULL != pNode->right))
{
if((element == pNode->left->element) || (element == pNode->right->element))
{
return TRUE; //查找成功
}
else if(element < pNode->left->element)
{
pNode = pNode->left; //查找元素值小于左子节点元素值,继续向左寻找
}
else if(element > pNode->right->element)
{
pNode = pNode->right; //查找元素值大于右子节点元素值,继续向右寻找
}
}
}
//查找失败,返回NULL
return FALSE;
}
然后将它的父节点和子节点相互连接,再删除节点即可。此步骤需要考虑一下三种情况。
(1)被删除的节点没有子节点时:这种情况只需要直接删除节点即可。
(2)被删除的节点只有一个子节点时:需要判断删除节点和父节点的大小关系,判断后将删除节点的父节点和子节点连接即可。
(3)被删除的节点有两个子节点时:根据树的定义,左子节点值<当前节点值<右子节点值,这里需要把预期节点替换成子节点中元素值最接近预期节点的值。就是将删除节点位置替换成左子树中的最大值或者右子树中的最小值。
/*函数功能:在二叉树中删除新元素
*函数参数:pTree-树的根节点地址,element-要查找的元素
*函数返回值:TRUE-删除成功,FALSE-删除失败
bool delete_tree(struct node *pTree, int element)
{
if(NULL == pTree)
{
return FALSE; //空树无法删除元素
}
//步骤一:查找删除节点和删除节点的父节点的位置
struct node *pNode = pTree;
struct node *pPreNode = pTree;
if((!find_node(pNode, element) || (!find_preNode(pPreNode, element)))
{
return FALSE; //在树中未找到元素,无法删除
}
//步骤二:将要删除节点的父节点和子节点相互连接
if((NULL == pNode->left) && (NULL == pNode->right)) //删除节点无子节点
{
//把删除节点的父节点指向NULL,并且把删除的节点free掉
pPreNode->next = NULL;
free(pNode);
}
else if((NULL == Node->left) || (NULL == Node->right)) //删除节点有一个子节点
{
//判断删除节点父节点和删除节点子节点的大小,实际上就是判断删除节点父节点和删除节点的大小
if(pPreNode->element > pNode->element)
{
pRevNode->left = pNode->left;
}
else
{
pRevNode->right = pNode->right;
}
free(pNode);
}
else //删除节点有两个子节点
{
//寻找删除节点左子树中的最大值
strcut node *pNodeMax = pNode->left;
strcut node *pNodePreMax = NULL;
int max_element = find_MaxElement(pNodeMax);
pNodePreMax = find_preNode(pNode->left , max_element);
//左子树最大值节点代替删除节点
pNodeMIN ->left = pNode->left;
pNodeMIN ->right = pNode->right;
//左子树最大值节点的父节点指向NULL
pNodePreMax ->right = NULL;
free(pNode);
}
return TRUE;
}