以二叉查找树为基础的树形数据结构在计算机中起到的作用不言而喻。最近在重温二叉树,主要是理解其中的递归和非递归实现的关联,并对二叉查找树的应用有更深的理解。其中,二叉查找树可以用来实现堆排序,可以用来形成高效率的查找树,由于其特殊的数据结构,在二叉树中的操作基本都是O(logn)的时间复杂度。下面慢慢细品。
递归操作都是与函数的调用栈有关,当查找一个元素时,查找到后,返回查找到的元素的指针,因为是递归调用,这个元素返回时是放回上一层的调用栈,这时候,需要上一层再返回这个指针,以此类推,就可以知道在递归调用中的return是不断向上一层调用栈返回的。插入元素更是如此,插入元素后,要修改这个元素的父节点指针,一遍插入节点能连接到它的父亲节点,这时候就需要在返回指针的上一层调用栈中就收这个指针,并将它放到相应位置,就需要不停的返回指针给上一层调用栈。
递归的调用栈跟非递归很相似,非递归算法大多需要栈来辅助完成递归算法的功能,这个就是递归与栈的相辅相成,用在回朔中比较多,下一篇文章就将二叉查找树的非递归算法。
二叉查找树的数据结构可以用来表示:
typedef struct tagTreeNode{
int idata; //节点数据,可以用模板实现,这里做演示,就用整形表示
tagTreeNode *lchild; //左子树
tagTreeNode *rchild; //右子树
unsigned int uiTimes; //重复的节点,则记录重复节点出现的次数
tagTreeNode(int iElement)
{
idata = iElement;
lchild = NULL;
rchild = NULL;
uiTimes = 1;
}
}TreeNode,*Position,*Root,*BinarySearchTree;
下面是二叉查找树的操作的接口定义,主要用来对递归和非递归的接口抽象接口:
class ISearchTree{
public:
virtual Position find(int iElement,const Root pRoot) = 0;
virtual Position findMin(const Root pRoot) = 0;
virtual Position findMax(const Root pRoot) = 0;
virtual Root insertNode(int iElement,Root pRoot) = 0;
virtual Root deleteNode(int iElement,Root pRoot) = 0;
virtual void preOrder(const Root pRoot ) = 0; //前序遍历
virtual void inOrder(const Root pRoot) = 0; //中序遍历
virtual void posOrder(const Root pRoot) = 0; //后序遍历
virtual void visit(const TreeNode* pNode) = 0; //访问节点逻辑
virtual Root getRoot() = 0;
};
下面的类是递归实现基本操作:
class CBinarySearchTreeRecursion : public ISearchTree
{
public:
CBinarySearchTreeRecursion(int iRootElement);
~CBinarySearchTreeRecursion(void);
virtual Position find(int iElement,const Root pRoot);
virtual Position findMin(const Root pRoot);
virtual Position findMax(const Root pRoot);
virtual Root insertNode(int iElement,Root pRoot); //重点内容
virtual Root deleteNode(int iElement,Root pRoot); //重点内容
virtual void preOrder(const Root pRoot );
virtual void inOrder(const Root pRoot);
virtual void posOrder(const Root pRoot);
virtual void visit(const TreeNode* pNode);
virtual Root getRoot();
private:
Root m_pRoot;
};
在构造函数中,传入的值可以作为根节点。下面是具体操作的详解。
在二叉查找树中查找元素是树的最基本的操作,查找操作也非常简单,递归查找左右子树,找到节点后返回节点的指针,如果没找到,则返回NULL,代码如下:
Position CBinarySearchTreeRecursion::find( int iElement,const Root pRoot)
{
if ( NULL == pRoot )//递归结束条件
{
return NULL;
}
if ( iElement < pRoot->idata)
{
return find(iElement,pRoot->lchild);
}else if (iElement > pRoot->idata)
{
return find(iElement,pRoot->rchild);
}else
{
return pRoot;//返还找到的节点
}
}
找最小元素,树中最小元素位于最左边的一个叶子节点,所以一直递归到最左边的叶子节点即可,代码如下:
Position CBinarySearchTreeRecursion::findMin(const Root pRoot)
{
if ( NULL == pRoot->lchild )//递归结束条件
{
return pRoot;
}
return findMin(pRoot->lchild);//往左边递归
}
找最大元素跟找最小元素类型,这里不啰嗦,代码如下:
Position CBinarySearchTreeRecursion::findMax(const Root pRoot)
{
if ( NULL == pRoot->rchild )//递归结束条件
{
return pRoot;
}
return findMax(pRoot->rchild);//往右边递归
}
插入一个节点,首先要找到合适的插入位置,找到位置后将新节点插入相应位置,并将节点指针返回,所以这里要注意返回值得处理和上一层的节点的指针处理:
Root CBinarySearchTreeRecursion::insertNode(int iElement,Root pRoot)
{
if ( NULL == pRoot )
{
pRoot = new TreeNode(iElement); //找到叶子节点后,插入这个叶子节点下面
}else if ( iElement < pRoot->idata )
{
pRoot->lchild = insertNode(iElement,pRoot->lchild); //递归查找左子树
}else if ( iElement > pRoot->idata)
{
pRoot->rchild = insertNode(iElement,pRoot->rchild); //递归查找右子树
} else
{
pRoot->uiTimes++; //插入的节点已经在二叉树中存在,则对出现的次数加1
return pRoot;
}
return pRoot;
}
删除一个节点,这个有点复杂,一般删除操作都要调整原有数据。一般用的删除策略是:找到要删除的节点后,将这个节点右子树的最小节点值与要删除的节点值交换,并且删除最小节点。这个有两个步骤:第一,找到要删除的节点,第二,找到该节点右子树的最小节点并删除。第二个步骤要低效一些,因为要遍历右子树。删除的时候,还有注意删除的节点是有左右子树还是只有一个子树。代码如下:
Root CBinarySearchTreeRecursion::deleteNode(int iElement,Root pRoot)
{
Position pMinOnRigth = NULL;
if ( NULL == pRoot )
{
return NULL;
}
if ( iElement < pRoot->idata )
{
pRoot->lchild = deleteNode(iElement,pRoot->lchild); //找到要删除的节点
}else if ( iElement > pRoot->idata )
{
pRoot->rchild = deleteNode(iElement, pRoot->rchild); //找到要删除的节点
}else
{
if ( pRoot->lchild && pRoot->rchild ) //找到要删除的节点进行删除,(两个孩子的情况)
{
pMinOnRigth = findMin( pRoot->rchild ); //找右子树最小节点
pRoot->idata = pMinOnRigth->idata; //这里的删除只是将要删除的节点值和右子树的值交换,最后一个叶子节点才释放内存
pRoot->rchild = deleteNode(pRoot->idata, pRoot->rchild);//删除右子树最小节点,相当于find,但是需要不断给右指针赋值
}else //这里才是实际的删除,0个或者1个孩子的情况
{
pMinOnRigth = pRoot;
if ( NULL == pRoot->lchild )
{
pRoot = pRoot->rchild;
}else if ( NULL == pRoot->rchild ) //处理没有右子树的情况
{
pRoot = pRoot->lchild;
}
delete pMinOnRigth;
pMinOnRigth = NULL;
}
}
return pRoot;
}
基本操作说完了,下面说说遍历,前中后遍历对于递归实现来说简洁明了,如下所示:
void CBinarySearchTreeRecursion::preOrder( const Root pRoot )
{
if ( NULL == pRoot )
{
return;
}
visit(pRoot);
preOrder(pRoot->lchild);
preOrder(pRoot->rchild);
}
void CBinarySearchTreeRecursion::inOrder( const Root pRoot )
{
if ( NULL == pRoot )
{
return;
}
inOrder(pRoot->lchild);
visit(pRoot);
inOrder(pRoot->rchild);
}
void CBinarySearchTreeRecursion::posOrder( const Root pRoot )
{
if ( NULL == pRoot )
{
return;
}
posOrder(pRoot->lchild);
posOrder(pRoot->rchild);
visit(pRoot);
}