二叉排序树(Binary Sort Tree),又称为二查搜索树。它要么是一颗空树,要么是符合性质的二叉树
- 若它的左子树不为空,那么左子树上的所有结点的值均小于它的根结点的值
- 若它的右子树不为空,那么右子树上的所有结点的值均大于它的根结点的值
- 它的左右子树也分别为二叉排序树
以上性质就造成了左结点<根结点<右结点,那么可以想象一下,如果对二叉排序树进行中序遍历,很明显,我们得到的是一个递增的序列。我们可以根据这个,来判断自己建立的二叉排序树的正确性。需要说明的是,二叉排序树虽然带排序俩字,但却不是为了排序,而是为了提高查找和插入删除的效率。
接下来,我们看一下,二叉排序树的查找是如何实现的。首先给出二叉树的结点结构体
//二叉树结点结构定义
typedef struct BSTNode
{
int m_data;
BSTNode *Lchild,*Rchild;
}BSTNode,*BiTree;
二叉排序树查找代码如下
//在二查搜索树中查找特定的值,返回值在树中的地址
//递归查找树pRoot中是否存在value
//指针pfather指向pRoot的双亲,当pRoot是开始的跟结点时,值为NULL
//指针pValue,指向找到value的结点,找不到则指向最后一个结点
bool SearchBST(BiTree pRoot,int value,BiTree pfather,BiTree *pValue)
{
if (!pRoot) //当最后发现value不存在时,pfther指向最后一个结点
{
*pValue = pfather;
return false;
}
else if (value == pRoot->m_data) //等于根结点,返回true
{
*pValue = pRoot;
return true;
}
else if (value < pRoot->m_data) //比根结点小,递归左子树查询
{
SearchBST(pRoot->Lchild,value,pRoot,pValue);
}
else
SearchBST(pRoot->Rchild,value,pRoot,pValue); //递归右子树查询
}
以上代码很好理解,接下我们看一下如何对二叉排序树进行插入操作。
//向二叉排序树中插入一个元素value
//*pRoot代表一颗二叉排序树
bool insertBST(BiTree * pRoot, int value)
{
BiTree p = NULL; //用于接收返回的查找的结点地址或路径中最后一个结点地址
if (!SearchBST(*pRoot,value,NULL,&p)) //二叉排序树中不能有重复的值
{
BiTree s = new BSTNode();
s->m_data = value;
s->Lchild = NULL;
s->Rchild = NULL;
if (NULL == *pRoot) //如果传过来的是一颗空树
{
*pRoot = s;
}
else if (value < p->m_data) //比最后一个结点小,放左边
{
p->Lchild = s;
}
else
p->Rchild = s; //否则放右边
return true;
}
else
{
cout<<"invalid value"<<endl;
return false;
}
}
插入有了,那么创建一颗二叉排序树的可想而知了
//创建二叉排序树
//state用于判断上一次插入是否成功,不成功返回
bool creatBinarySortTree(BiTree *pRoot,int *array, int size)
{
bool state = true;
for (int i = 0;(i < size)&&state; i++)
{
state = insertBST(pRoot, array[i]);
}
return state;
}
//销毁二查搜索树
void DestoryBST(BiTree pRoot)
{
if(NULL != pRoot)
{
DestoryBST(pRoot->Lchild); //释放左子树
DestoryBST(pRoot->Rchild); //释放右子树
delete pRoot;
}
}
好了,至此我们创建了一棵二叉排序树,可以对它进行查找,插入操作了,那么问题来了,我要是想对它进行删除怎么办?对于二叉排序树可是“请神容易送神难”。想想删除可那么简单,因为不能因为你删除了一个结点,这棵树就不是二叉排序树了吧?所以删除时,要考虑多种情况。
- 删除的是叶子结点,可以想象,删除叶子结点不会对其他结点照成任何影响,此种情况可以直接删除。
- 删除的结点仅有左子树或者右子树,这种情况也相对容易,我把那个结点删除了,把它的左子树或者右子树整体移动到它的位置就可以了。
- 左右子树都存在,那么找要删除结点的前驱或者后继去替换它,然后把它的前驱或者后继删掉。
接下来看看代码怎么怎么写
<pre name="code" class="cpp">bool deleteBST(BiTree *pRoot,int key)
{
if (!*pRoot) //删除的结点不存在
return false;
else
{
if (key == (*pRoot)->m_data) //找到值等于key的结点
return Delete(pRoot);
else if (key < (*pRoot)->m_data)
{
return deleteBST(&(*pRoot)->Lchild,key);
}
else
return deleteBST(&(*pRoot)->Rchild,key);
}
}
bool Delete(BiTree * pRoot)
{
BiTree q = NULL ,s = NULL;
if ((*pRoot)->Lchild == NULL) //左子树为空,重接右子树
{
q = *pRoot;
(*pRoot) = (*pRoot)->Rchild;
free(q);
}
else if ((*pRoot)->Rchild == NULL) //右子树为空,重接左子树
{
q =*pRoot;
*pRoot = (*pRoot)->Lchild;
free(q);
}
else
{
q = *pRoot; //q指向待删除的结点
s =(*pRoot)->Lchild; //
while (s->Rchild) //前驱没有右子树
{
q = s;
s = s->Rchild;
}
(*pRoot)->m_data = s->m_data; //s指向被删除结点的前驱
if (q != *pRoot)
{
q->Rchild = s->Lchild; //前驱的左孩子做前驱父节点的右孩子
}
else
{
q->Lchild = s->Lchild; //重接q的左子树
}
free(s);
}
return true;
}
二叉排序树的查找,走的就是根节点到要查找的结点路径,其比较次数等于给定值的结点在二叉树的深度,极端情况下,最少为1,即根节点就是要找的结点,最多不会超过树的深度,也就是说,查找的时间很大层度上取决于树的形状,但是二叉排序树的形状又是不确定的,那么问题有来了,由于形状的不同,查找的时间差异很大,怎么样才能保证树的深度呢,嘿嘿,往下就涉及到了平衡二叉树。