二叉查找树
对于树中的每个结点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值 大于X的关键字值 。
头文件head.h
#ifndef HEAD_H
#define HEAD_H
typedef int ElementType;
typedef struct node* SearchTree;
typedef struct node
{
ElementType data;
SearchTree Left;
SearchTree Right;
}TreeNode;
//清空二叉查找树
SearchTree MakeEmpty(SearchTree T);
//找元素
SearchTree Find(ElementType X, SearchTree T);
//找到最小值
SearchTree FindMin(SearchTree T);
//找到最大值
SearchTree FindMax(SearchTree T);
//插入元素
SearchTree Insert(ElementType X, SearchTree T);
//删除元素
SearchTree Delete(ElementType X, SearchTree T);
//中序遍历
void InOrderTree(SearchTree T);
//前序遍历
void PreOrderTree(SearchTree T);
//清除缓冲区
void clean(void);
#endif
二叉查找树的初始化MakeEmpty
与普通的二叉树清空相同。
SearchTree MakeEmpty(SearchTree T)
{
if (T != NULL)
{
MakeEmpty(T->Left);
MakeEmpty(T->Right);
free(T);
}
return NULL;
}
找到相应的元素Find
/*
* 函数名:Find
* 功能:在二叉树找到相应的元素
* 参数:
* X:被查找元素
* T:要查找的二叉树
* 返回值:
* 找到:返回查找元素结点指针
* 未找到:返回NULL
*/
SearchTree Find(ElementType X, SearchTree T)
{
if (T == NULL)
{
return NULL;
}
else if (X < T->data)
{
return Find(X, T->Left);
}
else if (X > T->data)
{
return Find(X, T->Right);
}
else
{
return T;
}
}
找到最小值FindMin
/*
* 函数名:FindMin
* 功能:找到二叉查找树中最小元素
* 参数:
* T:二叉树根结点指针
* 返回值:
* 空树:返回NULL
* 非空树:返回最左子树
*/
SearchTree FindMin(SearchTree T)
{
if (T != NULL)
{
while(T->Left)
{
T = T->Left;
}
}
return T;
}
同理找到最大元素FindMax
/*
* 函数名:FindMax
* 功能:找到二叉查找树中最大元素
* 参数:
* T:二叉树根结点指针
* 返回值:
* 空树:返回NULL
* 非空树:返回最右子树
*/
SearchTree FindMax(SearchTree T)
{
if (T != NULL)
{
while(T->Right)
{
T = T->Right;
}
}
return T;
}
插入元素Insert
/*
* 函数名:Insert
* 功能:在查找二叉树中插入相应元素(如果有重复的元素就不插入)
* 参数:
* X:要插入的元素
* T:二叉树根结点指针
* 返回值:
* 插入完成后的二叉树根结点指针
*/
SearchTree Insert(ElementType X, SearchTree T)
{
if (T == NULL)
{
T = (SearchTree)malloc(sizeof(TreeNode));
if (T == NULL)
{
fprintf(stderr, "out of space!\n");
exit(EXIT_FAILURE);
}
T->data = X;
T->Left = NULL;
T->Right= NULL;
return T;
}
else if (X < T->data)
{
// 如果插入元素小于当前结点,就去它的左子树插入。
// 注意返回值要给左子树的指针,因为插入后是一棵新树
T->Left = Insert(X, T->Left);
}
else if (X > T->data)
{
T->Right = Insert(X, T->Right);
}
else// 如果当前结点元素就是X,直接返回当前结点
{
return T;
}
}
删除元素Delete
最麻烦的就是删除。
- 如果结点是一片树叶,那么可以产即删除。
- 如果结点有一个儿子,则该结点可以在其父结点调整指针绕过该结点后删除。
如果要删除4元素代表的结点,应该为:
- 如果要删除的结点有两个儿子,如果删除这个结点就会非常的麻烦。可以转换为删除只有一个儿子的结点的方法。
一般方法是用其右子树的最小结点中的值覆盖这个结点的值(因为右树元素必大于当前结点元素,所以必大于当前结点的左树元素,而右树最小元素保证了其小于右树所有元素,满足当前结点元素大于所有左树元素小于所有右树元素的条件)再去删除右子树的最小元素的结点。
如果想要删除2所在元素的结点。可以找到其右子树最小值3,把3赋给2所在结点,再删除3这个结点。
/*
* 函数名:Delete
* 功能:删除相对应元素的位置
* 参数:
* X:要删除的元素
* T:二叉查找树根结点指针
* 返回值:删除结点后的树,其根结点指针
*/
SearchTree Delete(ElementType X, SearchTree T)
{
SearchTree temp;
//1. 如果树为空
if (T == NULL)
{
printf("The tree haven't data!\n");
}
// 找到X的位置
else if (X < T->data)
{
T->Left = Delete(X, T->Left);
}
else if (X > T->data)
{
T->Right = Delete(X, T->Right);
}
//如果X所在结点有两个儿子
else if (T->Left && T->Right)
{
temp = FindMin(T->Right); //找到右树最小元素
T->data = temp->data; //将右树最小元素赋结当前结点
//重新构造右子树
T->Right = Delete(T->data, T->Right); //递归的删除右树最小元素
}
//如果X所在结点小于两个儿子
else
{
temp = T;
if (T->Left == NULL)//这里也包含了左右树同时为空的情况
{
//T指向存在的儿子,因为递归。
//后面返回会的给到其父亲的左儿子或者右儿子指针
T = T->Right;
}
else if (T->Right == NULL)
{
T = T->Left;
}
printf("删除的元素为%d\n", temp->data);
free(temp);
}
//删除完毕
return T;
}
操作集operation.c
#include "head.h"
#include <stdio.h>
#include <stdlib.h>
//清空二叉查找树
SearchTree MakeEmpty(SearchTree T)
{
if (T != NULL)
{
MakeEmpty(T->Left);
MakeEmpty(T->Right);
free(T);
}
return NULL;
}
/*
* 函数名:Find
* 功能:在二叉树找到相应的元素
* 参数:
* X:被查找元素
* T:要查找的二叉树
* 返回值:
* 找到:返回查找元素结点指针
* 未找到:返回NULL
*/
SearchTree Find(ElementType X, SearchTree T)
{
if (T == NULL)
{
return NULL;
}
else if (X < T->data)
{
return Find(X, T->Left);
}
else if (X > T->data)
{
return Find(X, T->Right);
}
else
{
return T;
}
}
/*
* 函数名:FindMin
* 功能:找到二叉查找树中最小元素
* 参数:
* T:二叉树根结点指针
* 返回值:
* 空树:返回NULL
* 非空树:返回最左子树
*/
SearchTree FindMin(SearchTree T)
{
if (T != NULL)
{
while(T->Left)
{
T = T->Left;
}
}
return T;
}
/*
* 函数名:FindMax
* 功能:找到二叉查找树中最大元素
* 参数:
* T:二叉树根结点指针
* 返回值:
* 空树:返回NULL
* 非空树:返回最右子树
*/
SearchTree FindMax(SearchTree T)
{
if (T != NULL)
{
while(T->Right)
{
T = T->Right;
}
}
return T;
}
/*
* 函数名:Insert
* 功能:在查找二叉树中插入相应元素(如果有重复的元素就不插入)
* 参数:
* X:要插入的元素
* T:二叉树根结点指针
* 返回值:
* 插入完成后的二叉树根结点指针
*/
SearchTree Insert(ElementType X, SearchTree T)
{
if (T == NULL)
{
T = (SearchTree)malloc(sizeof(TreeNode));
if (T == NULL)
{
fprintf(stderr, "out of space!\n");
exit(EXIT_FAILURE);
}
T->data = X;
T->Left = NULL;
T->Right= NULL;
return T;
}
else if (X < T->data)
{
// 如果插入元素小于当前结点,就去它的左子树插入。
// 注意返回值要给左子树的指针,因为插入后是一棵新树
T->Left = Insert(X, T->Left);
}
else if (X > T->data)
{
T->Right = Insert(X, T->Right);
}
else// 如果当前结点元素就是X,直接返回当前结点
{
return T;
}
}
/*
* 函数名:Delete
* 功能:删除相对应元素的位置
* 参数:
* X:要删除的元素
* T:二叉查找树根结点指针
* 返回值:删除结点后的树,其根结点指针
*/
SearchTree Delete(ElementType X, SearchTree T)
{
SearchTree temp;
//1. 如果树为空
if (T == NULL)
{
printf("The tree haven't data!\n");
}
// 找到X的位置
else if (X < T->data)
{
T->Left = Delete(X, T->Left);
}
else if (X > T->data)
{
T->Right = Delete(X, T->Right);
}
//如果X所在结点有两个儿子
else if (T->Left && T->Right)
{
temp = FindMin(T->Right); //找到右树最小元素
T->data = temp->data; //将右树最小元素赋结当前结点
//重新构造右子树
T->Right = Delete(T->data, T->Right); //递归的删除右树最小元素
}
//如果X所在结点小于两个儿子
else
{
temp = T;
if (T->Left == NULL)//这里也包含了左右树同时为空的情况
{
//T指向存在的儿子,因为递归。
//后面返回会的给到其父亲的左儿子或者右儿子指针
T = T->Right;
}
else if (T->Right == NULL)
{
T = T->Left;
}
printf("删除的元素为%d\n", temp->data);
free(temp);
}
//删除完毕
return T;
}
//中序遍历
void InOrderTree(SearchTree T)
{
if (T == NULL)
{
printf("#");
return ;
}
InOrderTree(T->Left);
printf("%d ", T->data);
InOrderTree(T->Right);
}
//前序遍历
void PreOrderTree(SearchTree T)
{
if (T == NULL)
{
printf("#");
return ;
}
printf("%d ", T->data);
PreOrderTree(T->Left);
PreOrderTree(T->Right);
}
//清除缓冲区
void clean(void)
{
while(getchar() != '\n')
continue;
}
主函数main.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
int main(void)
{
ElementType data;
SearchTree T = NULL;
while (scanf("%d", &data))//输入元素,直到非数字
{
T = Insert(data, T);
}
//前序遍历
PreOrderTree(T);
printf("\n");
//中序遍历
InOrderTree(T);
printf("\n");
clean();
printf("输入你要查找的元素:");
scanf("%d", &data);
SearchTree record = Find(data, T);
printf("元素为%d\n", record->data);
clean();
printf("输入你要删除的元素:");
scanf("%d", &data);
Delete(data, T);
PreOrderTree(T);
printf("\n");
InOrderTree(T);
printf("\n");
//清空
T = MakeEmpty(T);
PreOrderTree(T);
printf("\n");
InOrderTree(T);
printf("\n");
return 0;
}
改进
//如果X所在结点有两个儿子
else if (T->Left && T->Right)
{
temp = FindMin(T->Right); //找到右树最小元素
T->data = temp->data; //将右树最小元素赋结当前结点
//重新构造右子树
T->Right = Delete(T->data, T->Right); //递归的删除右树最小元素
}
这段代码中,FindMin查找最小元素遍历了一次,Delte又遍历了一次,会浪费很多时间,可以写一个DeleteMin代替。
/*
* 函数名:DeleteMin
* 功能:删除给定树右子树中最小元素
* 参数:T:树根指针
* 返回值:最小元素的值
*/
ElementType DeleteMin(SearchTree T)
{
SearchTree record = T; //用于记录前一个结点指针
ElementType temp; //保存返回值
T = T->Right; //到右子树
if (T == NULL)//根据Delete,当T的左右都存在时,才调用此函数。故T不可能为NULL
{
fprintf(stdout, "error!\n");
exit(EXIT_FAILURE);
}
while(T->Left) //这里就可以看作单链表了(因为没有右结点什么事)
{
record = T;
T = T->Left;
}
record->Left = T->Right; //前一个结点指针,指向其右结点,因为左结点必为空
temp = T->data; //获取最小值
free(T);
return temp;
}
操作集operation.c
SearchTree Delete(ElementType X, SearchTree T)
{
SearchTree temp;
//1. 如果树为空
if (T == NULL)
{
printf("The tree haven't data!\n");
}
// 找到X的位置
else if (X < T->data)
{
T->Left = Delete(X, T->Left);
}
else if (X > T->data)
{
T->Right = Delete(X, T->Right);
}
//如果X所在结点有两个儿子
else if (T->Left && T->Right)
{
/*
temp = FindMin(T->Right);
T->data = temp->data;
T->Right = Delete(temp->data, T->Right);
*/
T->data = DeleteMin(T); //调用DeleteMin
}
//如果X所在结点小于两个儿子
else
{
temp = T;
if (T->Left == NULL)//这里也包含了左右树同时为空的情况
{
//T指向存在的儿子,因为递归。
//后面返回会的给到其父亲的左儿子或者右儿子指针
T = T->Right;
}
else if (T->Right == NULL)
{
T = T->Left;
}
printf("删除的元素为%d\n", temp->data);
free(temp);
}
//删除完毕
return T;
}
记得在头文件中加上DeleteMin。
两次运行结果一致。结果中删除的元素指得当然是右子树的最小结点上的元素。
可见是3。