搜索二叉树的概念
搜索二叉树满足下面两个要求:
(1)它是一棵二叉树
(2)该二叉树中,任意一棵树的根节点值大于它左子树中的所有结点的值,小于右子树中的所有结点的值
因此对于搜索二叉树的中序遍历来说,它是按由小到大依次递增的顺序排列的。
搜索二叉树的相关操作
在本文中将介绍搜索二叉树的以下操作:
(1)初始化搜索二叉树
(2)在搜索二叉树中插入指定元素(递归实现)
(3)在搜索二叉树中插入指定元素(非递归实现)
(4)在搜索二叉树中查找指定元素(递归实现)
(5)在搜索二叉树中查找指定元素(非递归实现)
(6)在搜索二叉树中删除指定元素(递归实现)
(7)在搜索二叉树中删除指定元素(非递归实现)
1. 搜索二叉树的表示
搜索二叉树本质上也是一棵二叉树,所以与二叉树的表示方法相同,可以根节点的指针来表示一棵搜索二叉树。因此,搜索二叉树的结点结构也与二叉树表示相同,可以利用孩子表示法来实现,至于各节点之间的大小对应关系,应在具体插入和删除时来保证。所以结点的结构表示如下:
//定义二叉搜索树的节点结构:孩子表示法 typedef char SearchTreeType; typedef struct SearchTreeNode { SearchTreeType data; struct SearchTreeNode* lchild; struct SearchTreeNode* rchild; }SearchTreeNode;
2. 搜索二叉树的初始化
搜索二叉树的结点结构定于与普通二叉树相同,因此在初始化时也相同。直接将根节点的指针置为空,来表示一棵空的搜索二叉树。
因为要对根节点的指针进行置空,即修改指针的值/指向,所以,这里在传参时应传递二级指针。
//初始化二叉搜索树 void SearchTreeInit(SearchTreeNode** proot) { if(proot == NULL) { //非法输入 return; } *proot = NULL; return; }
3. 在搜索二叉树中插入节点(递归实现)
要使在插入元素之后,依然为搜索二叉树,因此还必须满足搜索二叉树的第二个约束条件。所以对指定元素进行插入是,该结点所在的位置应当是固定的。
首先指定结点应作为某个节点的左孩子或右孩子。如果作为左孩子,则该结点的左子树必须为空,如果作为右孩子,则该节点的右子树必须为空。
其次作为右子树还是左子树,应根据指定元素与该结点的值进行大小判断,如果指定元素小于该结点的值,则作为该结点的左子树,如果大于,则作为该结点的右子树。
注意:这里规定搜索二叉树中的结点元素值是不重复的,如果新元素值已在树中存在,则不进行插入。
所以,需要不断的寻找满足上述两个要求的结点,思路如下:
(1)如果跟结点为空,则直接将根节点的指针指向新节点
(2)如果根节点不为空,则递归遍历寻找至少有一个子树为空的结点。
a)如果指定元素的值小于根节点的值,则递归遍历左子树,在左子树中寻找满足条件的值
b)如果指定元素的值大于根节点的值,则递归遍历右子树,在右子树中寻找满足条件的值
c)如果指定元素的值等于根节点的值,则直接返回,不进行插入。
代码如下:
//在二叉搜索树中插入元素 void SearchTreeInsert(SearchTreeNode** proot,SearchTreeType value) { if(proot == NULL) { //非法输入 return; } SearchTreeNode* new_node = CreateNode(value); if(*proot == NULL) { //插入之前树为空,则将根节点的指针指向新创建的节点 *proot = new_node; return; } //插入之前树不为空,则从根节点开始遍历查找应该插入的位置 //如果新节点的值小于根节点的值,则新节点应插入到左子树中 if(value < (*proot)->data) { //然后递归遍历左子树进行插入 SearchTreeInsert(&(*proot)->lchild,value); } //如果新节点的值大于根节点的值,则新节点应插入到右子树中 else if(value > (*proot)->data) { //然后递归遍历右子树进行插入 SearchTreeInsert(&(*proot)->rchild,value); } else { //如果相等,作如下规定:二叉搜索树中不允许有相等的节点 //此时,直接返回即可 return; } return; }
当搜索二叉树为单支树时,需遍历整棵树中的结点,所以该函数的时间复杂度为:O(n)。
4. 在搜索二叉树中插入指定元素结点(非递归实现)
在非递归实现中的思路与上述递归实现思路相同,只是在代码实现上略有不同。
(1)如果根节点为空,则使跟结点的指针指向新节点,结束操作
(2)如果根节点不为空,则将根节点作为当前节点cur,一层一层往下循环遍历寻找满足条件的结点
a)如果指定元素比根节点值小,则将当前节点cur替换为原cur的左子树,在左子树中继续循环寻找
b)如果指定元素比根节点大,则价将当前节点cur替换为原cur的右子树,在右子树中继续循环寻找
(3)当遍历到某一个结点时,
a)如果该结点的左子树为空,且指定元素小于该结点的值,则将新节点作为该结点的左子树
b)如果该结点的右子树为空,且指定元素大于该结点的值,则将新结点作为该结点的右子树
c)如果指定元素等于该结点的值,则直接结束返回,不进行插入。
代码如下:
//在二叉搜索树中插入指定元素(非递归) void SearchTreeInsertByLoop(SearchTreeNode** proot,SearchTreeType value) { if(proot == NULL) { //非法输入 return; } //创建新节点 SearchTreeNode* new_node = CreateNode(value); //如果根节点为空,直接使根节点的指针指向新节点 if(*proot == NULL) { *proot = new_node; return; } //从根节点开始循环遍历寻找新节点要插入的节点,该节点的左右子树必须至少有一个为空, //且与新节点的大小关系满足搜索二叉树的定义 //在插入新节点时,要一层一层的往下遍历直到找到可以插入的节点 SearchTreeNode* cur = *proot; while(1) { //如果某个节点的左子树为空,且value的值小于该节点的值 //将新节点作为该节点的左子树 if(cur->rchild == NULL && value > cur->data) { cur->rchild = new_node; break; } //如果某个节点的左子树不为空,且value的值小于该节点的值 //则在该节点的左子树中继续循环遍历查找 if(value < cur->data) { cur = cur->lchild; } //如果某个节点的右子树不为空,且value的值大于该节点的值 //则在该节点的右子树中继续循环遍历查找 else if(value > cur->data) { cur = cur->rchild; } //如果遇到与某个节点的值相等,则直接结束,不进行插入 else { DestroyNode(new_node);//释放新创建的节点 break; } } return; }
5. 在二叉树中查找指定元素(递归)
在上述的插入操作中,其实已经包含该步操作。当遍历到一个结点,就将指定元素与该结点的值进行比较。思路如下:
(1)如果该树给空,则查找失败
(2)如果树不为空,则比较跟结点与指定元素的大小
a)如果指定元素比跟结点值小,此时需要在跟结点的左子树中寻找,因此需要递归遍历左子树
b)如果指定元素比跟结点大,此时需要在跟结点的右子树中寻找,因此需要递归遍历右子树
c)如果相等,则直接返回根节点的指针。
代码如下:
//在二叉搜索树中查找指定元素 SearchTreeNode* SearchTreeFind(SearchTreeNode* root,SearchTreeType to_find) { if(root == NULL) { //空树 return NULL; } //首先判断根节点处的值与所要查找元素的大小关系,如果相等,则直接返回根节点的指针 if(root->data == to_find) { return root; } else if(to_find < root->data)//如果要查找节点的值小于根节点的值 { //则在左子树中递归遍历查找 return SearchTreeFind(root->lchild,to_find); } else//如果要查找节点的值大于根节点的值 { //则在右子树中递归遍历查找 return SearchTreeFind(root->rchild,to_find); } }
6. 在二叉树中查找指定元素(非递归)
非递归实现时与上述递归实现思路相同,
(1)判断跟节点是否为空,为空,则查找失败
(2)不为空,则将根节点作为当前节点cur,
a)如果指定元素小于根节点的值,则将cur更新为cur的左子树,循环(2)
b)如果指定元素大于根节点的值,则将cur更新为cur的右子树,循环(2)
c)如果指定元素等于根节点的值,则返回cur的值
d)如果遍历到cur为空时还没找到,说明要查找的元素不存在,直接跳出循环返回即可。
代码如下:
//在二叉搜索树中查找指定元素(非递归) SearchTreeNode* SearchTreeFindByLoop(SearchTreeNode* root,SearchTreeType to_find) { if(root == NULL) { //空树 return NULL; } //从根节点开始遍历查找,待找到某个节点 SearchTreeNode* cur = root; while(1) { //如果遍历到某个节点时,该节点为空,则说明要查找的值不存在 if(cur == NULL) { break; } //如果要查找节点的值小于根节点的值,则在左子树中循环遍历查找 if(to_find < cur->data) { cur = cur->lchild; } //rug要查找节点的值大于根节点的值,则在该节点的右子树中继续循环遍历查找 else if(to_find > cur->data) { cur = cur->rchild; } else { return cur; } } return NULL; }
7. 在二叉搜索树中删除指定元素(非递归实现)
(1)在树中删除元素之前,首先要判断这棵树是否为空,为空则删除失败。
(2)树不为空,此时需要在树中查找指定元素所在的位置。如果没找到,也删除失败
(3)如果找到了,根据删除元素的不同状态可分为以下几种情况讨论。
a)如果要删除的结点没有左右子树
i)如果此时要删除的结点是根节点,此时直接将存放根节点指针的内存空间置空。
ii)如果此时要删除的结点不是根节点。
若要删除结点是其父节点的左子树,此时需要将父节点的左子树置为空
若要删除节点是其父节点的右子树,此时需要将父节点的右子树置为空
最后释放根节点的内存空间即可。
b)要删除的结点只有左子树
i)如果此时要删除的结点是根节点,则将存放根节点指针的内存空间用于存放原根节点的左子树的指针
ii)如果此时要删除的结点不是根节点。
若要删除结点是其父节点的左子树,则将要删除
c)要删除的结点只有右子树
d)要删除的结点左右子树都有