二叉查找树之——非递归实现

        上一一篇文章介绍了二叉查找树的递归实现,递归算法以简洁明了,便于阅读与理解,但是只要是与递归有关,就少不了递归调用栈,也就是函数栈,这会导致栈溢出。假如递归层次达到10000次,程序就崩溃了,二叉查找树当插入的节点是升序或者逆序,就形成了链表,这样就形成了10000层递归。并且,递归没有非递归效率高,语音在于函数调用会相对耗时。所以非递归算法在安全性和效率上更实用。下面对二叉查找操作的非递归算法实现。

        查找节点,很简单,通过循环来查找,代码如下:

Position CBinarySearchTreeNoRecursion::find( int iElement,const Root pRoot )
{
     if ( NULL == pRoot )
     {
		 return NULL;
     }

     TreeNode* pNode = pRoot;

     while ( pNode != NULL )
     {
		 if ( iElement < pNode->idata )
		 {
             pNode = pNode->lchild;

		 }else if ( iElement > pNode->idata )
		 {
			 pNode = pNode->rchild;

		 }else
		 {
			 visit(pNode);
			 return pNode;   //找到则返回
		 }
     }
	 return NULL;
}

        查找最小元素,一直往左走,找到后访问,代码如下:

Position CBinarySearchTreeNoRecursion::findMin( const Root pRoot )
{
	if ( NULL == pRoot )
	{
		return NULL;
	}

    TreeNode *pNode = pRoot;

    while ( pNode->lchild != NULL) //一直走到最左边
	{
        pNode = pNode->lchild;
	}

	visit(pNode);
	return pNode;
}
      查找最大元素,一直往右走,找到后访问,代码如下:

Position CBinarySearchTreeNoRecursion::findMax( const Root pRoot )
{
	if ( NULL == pRoot )
	{
		return NULL;
	}

	TreeNode *pNode = pRoot;

	while ( pNode->rchild != NULL) //一直走到最右
	{
		pNode = pNode->rchild;
	}

	visit(pNode);
	return pNode;
}
       插入一个节点,就要找到位置后插入,因为找到后,遍历指针为NULL,就需要一个指针尾随着遍历指针,这样就可以插入后返回正确位置的指针,代码如下:

Root CBinarySearchTreeNoRecursion::insertNode( int iElement,Root pRoot )
{
	if ( NULL == pRoot )
	{
		return NULL;
	}

	TreeNode* pNode = NULL;
	TreeNode* pTemp = pRoot;

	while ( pRoot != NULL )   //找到插入的位置
	{   
		pNode = pRoot;//保存上一个节点的指针

		if ( iElement < pRoot->idata )
		{
			pRoot = pRoot->lchild;

		}else if (iElement > pRoot->idata )
		{
			pRoot = pRoot->rchild;

		}else
		{
			pRoot->uiTimes++;
			return pTemp;
		}
	}

	if ( iElement < pNode->idata)   //插入该位置的左边还是右边
	{
		pNode->lchild = new TreeNode(iElement);

	}else if (iElement > pNode->idata )
	{
		pNode->rchild = new TreeNode(iElement);
	}

	return pNode;
}
       二叉树的非递归删除也是很复杂,首先,让我们用一个公用的删除策略,用删除的节点的右子树的最小节点代替被删除节点,这样就可以满足二叉树的定义:左小右大。删除时,又要看看特殊情况,如果删除的节点没有右孩子怎么处理?只有一个节点或者没有节点怎么处理?下面让我们来分情况讨论:

       删除的节点有左右子树:选取右子树的最小值代替删除节点,然后删除最小节点。

       删除的节点只有一个或着没有孩子:需要用一个指针跟踪要删除的节点的上一个节点位置,这样就可以删除节点后设置它的父亲节点左右子树正确的指针。

       更多细节请看如下代码:

Root CBinarySearchTreeNoRecursion::deleteNode( int iElement,Root pRoot )
{
	if ( NULL == pRoot )
	{
		return pRoot;
	}
    
	Root pNode       = pRoot;       //遍历用的指针
	Root pLastNode   = pNode;       //遍历指针的上一个节点的指针
	Root pMinOnRight = NULL;        //右子树最小节点的指针
	Root pSecondMinOnRight = NULL;  //右子树次最小节点的指针

    while ( pNode != NULL )
	{ 
        if ( iElement < pNode->idata )
        {
			pLastNode = pNode;
			pNode     = pNode->lchild;
        }else if ( iElement > pNode->idata )
        {
			pLastNode = pNode;
			pNode     = pNode->rchild;
        }else   //找到要删除的节点
		{
			if ( pNode->lchild != NULL && pNode->rchild != NULL )//有两个孩子情况
			{
				pSecondMinOnRight = pNode;
				pMinOnRight       = pNode->rchild;

				while ( pMinOnRight->lchild != NULL)      //删除策略:用右子树最小节点代替要删除的节点。找到最小的和次最小的节点
				{
					pSecondMinOnRight = pMinOnRight;
					pMinOnRight       = pMinOnRight->lchild;  
				}    

				pNode->idata = pMinOnRight->idata;  //将找到的最小节点放到要删除的节点位置,其实是值交换
                
				if ( NULL == pMinOnRight->rchild )  //找到的最小节点是叶子节点
				{
					pSecondMinOnRight->lchild = NULL;
				}else                                 //找到的最小节点是非叶子节点,则要处理它的右孩子,将右孩子连接到最小节点的父亲节点处
				{
					pSecondMinOnRight->lchild = pMinOnRight->rchild;
				}
				printf("delete the  element: %d\n",pMinOnRight->idata);
				delete pMinOnRight;
				pMinOnRight = NULL;
				return pRoot;

			}else//一个或者零个孩子的情况
			{    
				 if ( pLastNode->lchild == pNode )     //要删除的节点是左子树还是右子树
				 {
					 if ( NULL == pNode->lchild )      //删除的节点是左节点
					 {
						 pLastNode->lchild = pNode->rchild;   //如果删除节点的左子树是空,则返回右子树
					 }else if ( NULL == pNode->rchild )
					 {
						 pLastNode->lchild = pNode->lchild;//如果删除节点的右子树是空,则返回左子树
					 }
				 } 
				 else if( pLastNode->rchild == pNode )//删除的节点是右节点
				 {
					 if ( NULL == pNode->lchild )     
					 {
						 pLastNode->rchild = pNode->rchild;
					 }else if ( NULL == pNode->rchild )
					 {
						 pLastNode->rchild = pNode->lchild;
					 }
				 }
				 printf("delete the  element: %d\n",pNode->idata);
				 delete pNode;
				 pNode = NULL;
				 return pRoot;
			}
		}
	}

	return pRoot;
}
       下面来讲解遍历操作,遍历需要用到栈来保存将要被访问的节点指针,遍历有一个特点,就是中后遍历,只要遍历的左孩子,就是相当于遍历了左节点和跟节点,所以主要关注的是右子树。

       前序遍历:根左右。先跟后左右子树,这个代码很好写,只需要将根节点入栈,然后出栈访问后再将其左右子树入栈,然后重复这个过程直到栈空,代码如下:

void CBinarySearchTreeNoRecursion::preOrder( const Root pRoot )
{
    if ( NULL == pRoot )
    {
		return;
    }
   
    stack<TreeNode*> nodeStack;
	nodeStack.push(pRoot);
	TreeNode* pNode = pRoot;

    while ( !nodeStack.empty() )
	{
		pNode = nodeStack.top();
		nodeStack.pop();

		visit(pNode);//访问根节点的逻辑

		if ( pNode->rchild != NULL )
		{
			nodeStack.push(pNode->rchild);
		}
		if ( pNode->lchild != NULL )
		{
			nodeStack.push(pNode->lchild);
		}
	}
}
       中序的遍历解放方案是:将一直往左找,找到最左边的节点后,访问该节点,然后出栈,将它的右子树入栈访问,重复这个过程。代码如下:

void CBinarySearchTreeNoRecursion::inOrder( const Root pRoot )
{
	if ( NULL == pRoot )
	{
		return;
	}

	stack<TreeNode*> nodeStack;
	TreeNode* pNode = pRoot;

	while ( pNode || !nodeStack.empty() )
	{
		if ( pNode != NULL )
		{
			nodeStack.push(pNode);        //遍历左子树
			pNode = pNode->lchild;

		}else
		{
			pNode = nodeStack.top();
            nodeStack.pop();
			visit(pNode);//访问左节点的逻辑
			pNode = pNode->rchild;   //遍历右子树
		}
	}
}
        后序遍历比较复杂,因为不能单单通过入栈出栈来全部访问,必须设置一个标志,标志这个节点的右子树是否已经访问了,如果没有访问,则访问,否则将遍历右子树。

        删除策略:首先将左边全部入栈,然后出栈访问,看看它的右子树是否已经访问,如果没有,则访问右子树否则访问它。必须用到一个辅助数据结构,这里用一个标志栈来与节点栈匹配,看对应的节点栈顶元素的右子树是否已经访问,false为未访问,true为已访问。

void CBinarySearchTreeNoRecursion::posOrder( const Root pRoot )
{
    if ( NULL == pRoot )
    {
		return;
    }

	TreeNode* pNode = pRoot;
	stack<TreeNode*> nodeStack;   
	stack<bool>      flageStack;  //标志栈,false:未访问,true:已访问

	while ( pNode != NULL )       //将树的最左边节点全部入栈
	{
		nodeStack.push(pNode);
		flageStack.push(false);   //这两个栈有同时出入,节点栈出栈,则标志栈也要出入。
		pNode = pNode->lchild;
	}

	while ( !nodeStack.empty() )
	{
        pNode = nodeStack.top();
		if (NULL == pNode->rchild || flageStack.top() )  //如果没有右子树或者右子树已经访问,则可以访问这个节点。
		{
            nodeStack.pop();
			flageStack.pop();
			visit(pNode);
		}else        //右孩子存在并且这个节点没有被访问,则遍历右子树
		{
			flageStack.pop();
			flageStack.push(true);   //因为下面将右子树入栈,右子树后肯定会先于它被访问
                                     //所以这里可以设置这个节点的右孩子已经被访问。
			pNode = pNode->rchild;
            while ( pNode != NULL )  //遍历右子树
			{
				nodeStack.push(pNode);
				flageStack.push(false);
				pNode = pNode->lchild;
            }
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值