二叉查找树
什么是二叉查找树?
- 是树的一种,即度为2的树。简单点说就是,每个节点至多只有两个子节点(也就是说存在0、1、2个结点);
- 描述的是一种一对多的关系,可由一个根节点衍生出一棵左子树,一棵右子树,每一棵子树又可以看成一一个根节点发散的树;
节点
据此我们可以定义树的基本单元为一个节点包含:Data域和指针域(两个指针,一个指向节点,一个指向右节点)
typedef struct _node{
ElemType item; //抽象高级数据类型,可用结构自定义
struct _node * left;
struct _node * right;
}Node;
二叉树
有了定义的基本二叉节点类型,我们可以开始定义我们的二叉树类型,树中包含:二叉树根节点指针和当前树节点数量size(增加这个size的目的是为了后续的操作可以更方便);
typedef struct _BinaryTree{
Node * root;
int size;
}BinaryTree;
接口以及实现
一、初始化
//将树初始化为空树
void InitializeTree(BinaryTree * bt)
{
bt->root = NULL;
size = 0;
}
二、是否为空
//判断是否是一棵空树
bool TreeIsEmpty(const BinaryTree * bt)
{
return size == 0;
}
三、是否已满
//假设我们已经定义了树的节点上限为MAXSIZE
#define MAXSIZE 20
bool TreeIsFull(const BinaryTree * bt)
{
return size == MAXSIZE;
}
四、返回当前树节点数量
int TreeCount(const BinaryTree * bt)
{
return bt->size;
}
五、添加节点(非递归)
static CopyToNode(Node * pnew, const ElemType * pt);
static bool Toleft(const ElemType m1, const ElemType m2);
static bool Toright(const ElemType m1, const ElemType m2);
//该函数返回是否添加成功
bool AddToTree(BinaryTree * bt, const ElemType * pt)
{
if(bt->size == MAXSIZE) //已满
return false;
Node * pnew = (Node *) malloc(sizeof(Node));
if(pnew == NULL) //内存分配失败
return false;
pnew->left = pnew->right = NULL;
CopyToNode(pnew,pt); //根据元素类型需要创建的辅助函数
if(bt->root == NULL) //添加第一个根节点
bt->root = pnew;
else{ //添加后续
Node * p = root;
while(p) //找到正确叶子节点的空子节点(区分左右)
if(Toleft(p->item, *pt))
p = p->left;
else if(Toright(p->item),*pt)
p = p->right;
else
return false; //假定树中不能存在相同元素
p = pnew; //链接
}
bt->size++;
return true;
}
六、删除节点(递归法)
删除节点考虑,考虑删除的节点可能有四种情况:
- 叶子节点,此节点度为0;这种情况较容易,可直接释放当前节点,将父节点设为NULL即可;
- 仅含左节点,将父节点指向左子节点,释放当前节点即可;
- 仅含右节点,将父节点指向右子节点,释放当前节点即可;
- 含左右节点,释放当前节点,将父节点指向左子树,沿着左子树向下查找一个空的右子节点位置,将右子树连接上去即可;
从分析来看我们是需要知道待删除节点的父节点的,此处省略查找父节点的函数,假设我们有一个函数可返回相关信息,我们将其存放在一个结构中定义为Pair类型的一个look变量,包含父节点parent成员以及当前要删除的节点child。
//seekitem()函数返回的结构类型,局部数据类型
typedef struct pair
{
Node * parent;
Node * child;
}Pair;
static DeleteNode(Node ** pt)
{
Node * temp;
if((*pt)->left == NULL)
{
temp = (*pt);
(*pt) = (*pt)->right;
free(temp);
}else if((*pt)->right == NULL)
{
temp = (*pt);
(*pt) = (*pt)->left;
free(temp);
}else{
for(temp = (*pt)->left;temp->right;temp = temp->right)
continue;
temp->right = (*pt)->right;
temp = (*pt);
(*pt) = (*pt)->left;
free(temp);
}
}
bool DelInTree(BinaryTree * bt, const ElemType * pt)
{
Pair look;
look = SeekElem(bt,pt);
if(look.child == NULL)
return false; //未找到
if(look.parent == NULL)
DeleteNode(&bt->root);
else if(look.parent->left == look.child)
DeleteNode(&look.parent->left);
else
DeleteNode(&look.parent->right);
bt->size--;
return true;
}
七、清空树(递归)
清空树涉及到遍历树的相关方法,其实其他查找之类的也要用到遍历,此处我们仅写一个遍历删除树的方法,其他操作可以类似的遍历。
遍历二叉树包括三种方式:
- 先序遍历,先根,根->左子->右子
- 后序遍历,后根,左子->右子->根
- 中序遍历,中根,左子->根->右子
下面采用中序遍历的删除方式:
static void DeleteAllNode(Node * root)
{
Node * p;
if(root != NULL)
{
//暂存右子树
p = root->right;
//删除左子树
DeleteAllNode(root->left);
//释放根节点
free(root);
//删除右子树
DeleteAllNode(pright);
}
}
void ClearTree(BinaryTree * bt)
{
if(bt)
DeleteAllNode(bt->root);
bt->root = NULL;
bt->size = 0;
}
小结
关于二叉树的添加、删除、查找就到这,基于这些基础操作我们可以定义更多的相关操作,添加时需要做一些判断,删除时也需要判断树是否为空是否找到之类,查找也涉及到树的遍历,树的遍历有三种方式:先序遍历、中序遍历、后序遍历。由于树本身的定义就是递归的,除了用非递归的方式写相关函数,也可以用递归的方式写,递归遍历的时候我们只需要知道根节点就可以了,每一次递归调用传入下一个位置,又将下个位置视为根节点,直到某个返回条件。